fractal-server 2.17.1a1__py3-none-any.whl → 2.18.0__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 (225) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +21 -19
  3. fractal_server/app/db/__init__.py +3 -3
  4. fractal_server/app/models/__init__.py +1 -0
  5. fractal_server/app/models/linkuserproject.py +43 -1
  6. fractal_server/app/models/security.py +28 -8
  7. fractal_server/app/models/v2/__init__.py +3 -1
  8. fractal_server/app/models/v2/accounting.py +9 -1
  9. fractal_server/app/models/v2/dataset.py +5 -1
  10. fractal_server/app/models/v2/history.py +15 -1
  11. fractal_server/app/models/v2/job.py +17 -2
  12. fractal_server/app/models/v2/profile.py +29 -0
  13. fractal_server/app/models/v2/project.py +4 -10
  14. fractal_server/app/models/v2/resource.py +17 -0
  15. fractal_server/app/models/v2/task_group.py +4 -3
  16. fractal_server/app/models/v2/workflow.py +2 -1
  17. fractal_server/app/routes/admin/v2/__init__.py +12 -13
  18. fractal_server/app/routes/admin/v2/accounting.py +3 -3
  19. fractal_server/app/routes/admin/v2/job.py +35 -24
  20. fractal_server/app/routes/admin/v2/profile.py +3 -2
  21. fractal_server/app/routes/admin/v2/resource.py +5 -5
  22. fractal_server/app/routes/admin/v2/sharing.py +103 -0
  23. fractal_server/app/routes/admin/v2/task.py +37 -26
  24. fractal_server/app/routes/admin/v2/task_group.py +94 -17
  25. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +21 -22
  26. fractal_server/app/routes/api/__init__.py +1 -9
  27. fractal_server/app/routes/api/v2/__init__.py +49 -50
  28. fractal_server/app/routes/api/v2/_aux_functions.py +132 -124
  29. fractal_server/app/routes/api/v2/_aux_functions_history.py +51 -23
  30. fractal_server/app/routes/api/v2/_aux_functions_sharing.py +97 -0
  31. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +6 -8
  32. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +7 -9
  33. fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +1 -2
  34. fractal_server/app/routes/api/v2/dataset.py +95 -102
  35. fractal_server/app/routes/api/v2/history.py +59 -33
  36. fractal_server/app/routes/api/v2/images.py +24 -9
  37. fractal_server/app/routes/api/v2/job.py +52 -33
  38. fractal_server/app/routes/api/v2/pre_submission_checks.py +16 -8
  39. fractal_server/app/routes/api/v2/project.py +65 -37
  40. fractal_server/app/routes/api/v2/sharing.py +311 -0
  41. fractal_server/app/routes/api/v2/status_legacy.py +31 -41
  42. fractal_server/app/routes/api/v2/submit.py +82 -78
  43. fractal_server/app/routes/api/v2/task.py +19 -20
  44. fractal_server/app/routes/api/v2/task_collection.py +41 -43
  45. fractal_server/app/routes/api/v2/task_collection_custom.py +19 -20
  46. fractal_server/app/routes/api/v2/task_collection_pixi.py +10 -11
  47. fractal_server/app/routes/api/v2/task_group.py +25 -24
  48. fractal_server/app/routes/api/v2/task_group_lifecycle.py +32 -32
  49. fractal_server/app/routes/api/v2/task_version_update.py +23 -19
  50. fractal_server/app/routes/api/v2/workflow.py +50 -55
  51. fractal_server/app/routes/api/v2/workflow_import.py +37 -37
  52. fractal_server/app/routes/api/v2/workflowtask.py +32 -26
  53. fractal_server/app/routes/auth/__init__.py +1 -3
  54. fractal_server/app/routes/auth/_aux_auth.py +101 -2
  55. fractal_server/app/routes/auth/current_user.py +2 -66
  56. fractal_server/app/routes/auth/group.py +8 -35
  57. fractal_server/app/routes/auth/login.py +1 -0
  58. fractal_server/app/routes/auth/oauth.py +4 -3
  59. fractal_server/app/routes/auth/register.py +4 -2
  60. fractal_server/app/routes/auth/router.py +2 -0
  61. fractal_server/app/routes/auth/users.py +19 -10
  62. fractal_server/app/routes/auth/viewer_paths.py +43 -0
  63. fractal_server/app/routes/aux/_job.py +1 -1
  64. fractal_server/app/routes/aux/_runner.py +2 -2
  65. fractal_server/app/routes/pagination.py +1 -1
  66. fractal_server/app/schemas/user.py +29 -12
  67. fractal_server/app/schemas/user_group.py +0 -15
  68. fractal_server/app/schemas/v2/__init__.py +55 -48
  69. fractal_server/app/schemas/v2/accounting.py +11 -0
  70. fractal_server/app/schemas/v2/dataset.py +57 -11
  71. fractal_server/app/schemas/v2/dumps.py +10 -9
  72. fractal_server/app/schemas/v2/job.py +11 -11
  73. fractal_server/app/schemas/v2/manifest.py +4 -3
  74. fractal_server/app/schemas/v2/profile.py +53 -2
  75. fractal_server/app/schemas/v2/project.py +3 -3
  76. fractal_server/app/schemas/v2/resource.py +121 -16
  77. fractal_server/app/schemas/v2/sharing.py +99 -0
  78. fractal_server/app/schemas/v2/status_legacy.py +3 -3
  79. fractal_server/app/schemas/v2/task.py +6 -7
  80. fractal_server/app/schemas/v2/task_collection.py +5 -5
  81. fractal_server/app/schemas/v2/task_group.py +16 -16
  82. fractal_server/app/schemas/v2/workflow.py +16 -16
  83. fractal_server/app/schemas/v2/workflowtask.py +16 -15
  84. fractal_server/app/security/__init__.py +5 -8
  85. fractal_server/app/security/signup_email.py +4 -5
  86. fractal_server/app/shutdown.py +6 -6
  87. fractal_server/config/__init__.py +0 -6
  88. fractal_server/config/_data.py +0 -68
  89. fractal_server/config/_database.py +19 -20
  90. fractal_server/config/_email.py +30 -38
  91. fractal_server/config/_main.py +38 -52
  92. fractal_server/config/_oauth.py +17 -21
  93. fractal_server/data_migrations/2_18_0.py +30 -0
  94. fractal_server/exceptions.py +4 -0
  95. fractal_server/images/models.py +4 -5
  96. fractal_server/images/status_tools.py +4 -2
  97. fractal_server/logger.py +1 -1
  98. fractal_server/main.py +75 -13
  99. fractal_server/migrations/versions/034a469ec2eb_task_groups.py +4 -8
  100. fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +1 -1
  101. fractal_server/migrations/versions/0f5f85bb2ae7_add_pre_pinned_packages.py +1 -0
  102. fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +1 -1
  103. fractal_server/migrations/versions/1a83a5260664_rename.py +1 -1
  104. fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +1 -0
  105. fractal_server/migrations/versions/316140ff7ee1_remove_usersettings_cache_dir.py +1 -1
  106. fractal_server/migrations/versions/40d6d6511b20_add_index_to_history_models.py +47 -0
  107. fractal_server/migrations/versions/45fbb391d7af_make_resource_id_fk_non_nullable.py +1 -1
  108. fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +1 -0
  109. fractal_server/migrations/versions/49d0856e9569_drop_table.py +2 -3
  110. fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +1 -1
  111. fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +1 -1
  112. fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +2 -1
  113. fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +7 -19
  114. fractal_server/migrations/versions/5bf02391cfef_v2.py +4 -10
  115. fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +1 -0
  116. fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +1 -1
  117. fractal_server/migrations/versions/7673fe18c05d_remove_project_dir_server_default.py +1 -1
  118. fractal_server/migrations/versions/7910eed4cf97_user_project_dirs_and_usergroup_viewer_.py +60 -0
  119. fractal_server/migrations/versions/791ce783d3d8_add_indices.py +1 -1
  120. fractal_server/migrations/versions/83bc2ad3ffcc_2_17_0.py +1 -0
  121. fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +1 -0
  122. fractal_server/migrations/versions/88270f589c9b_add_prevent_new_submissions.py +39 -0
  123. fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +2 -4
  124. fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +1 -1
  125. fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +1 -0
  126. fractal_server/migrations/versions/969d84257cac_add_historyrun_task_id.py +1 -1
  127. fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +1 -1
  128. fractal_server/migrations/versions/981d588fe248_add_executor_error_log.py +1 -1
  129. fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +2 -4
  130. fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +1 -1
  131. fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +1 -1
  132. fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +1 -1
  133. fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +1 -1
  134. fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +1 -0
  135. fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +1 -0
  136. fractal_server/migrations/versions/b1e7f7a1ff71_task_group_for_pixi.py +1 -1
  137. fractal_server/migrations/versions/b3ffb095f973_json_to_jsonb.py +1 -0
  138. fractal_server/migrations/versions/bc0e8b3327a7_project_sharing.py +72 -0
  139. fractal_server/migrations/versions/c90a7c76e996_job_id_in_history_run.py +1 -1
  140. fractal_server/migrations/versions/caba9fb1ea5e_drop_useroauth_user_settings_id.py +1 -1
  141. fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +4 -9
  142. fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +1 -0
  143. fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +1 -1
  144. fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +1 -0
  145. fractal_server/migrations/versions/e0e717ae2f26_delete_linkuserproject_ondelete_project.py +50 -0
  146. fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +1 -0
  147. fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +1 -1
  148. fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +1 -0
  149. fractal_server/migrations/versions/f0702066b007_one_submitted_job_per_dataset.py +40 -0
  150. fractal_server/migrations/versions/f37aceb45062_make_historyunit_logfile_required.py +1 -1
  151. fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +1 -0
  152. fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +4 -9
  153. fractal_server/runner/config/_local.py +8 -5
  154. fractal_server/runner/config/_slurm.py +39 -33
  155. fractal_server/runner/config/slurm_mem_to_MB.py +0 -1
  156. fractal_server/runner/executors/base_runner.py +29 -4
  157. fractal_server/runner/executors/local/get_local_config.py +1 -0
  158. fractal_server/runner/executors/local/runner.py +14 -13
  159. fractal_server/runner/executors/slurm_common/_batching.py +9 -20
  160. fractal_server/runner/executors/slurm_common/base_slurm_runner.py +53 -27
  161. fractal_server/runner/executors/slurm_common/get_slurm_config.py +14 -7
  162. fractal_server/runner/executors/slurm_common/remote.py +3 -1
  163. fractal_server/runner/executors/slurm_common/slurm_config.py +2 -0
  164. fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -3
  165. fractal_server/runner/executors/slurm_ssh/runner.py +16 -11
  166. fractal_server/runner/executors/slurm_ssh/tar_commands.py +1 -0
  167. fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +1 -0
  168. fractal_server/runner/executors/slurm_sudo/runner.py +16 -11
  169. fractal_server/runner/task_files.py +9 -3
  170. fractal_server/runner/v2/_local.py +12 -6
  171. fractal_server/runner/v2/_slurm_ssh.py +14 -7
  172. fractal_server/runner/v2/_slurm_sudo.py +14 -7
  173. fractal_server/runner/v2/db_tools.py +0 -1
  174. fractal_server/runner/v2/deduplicate_list.py +2 -1
  175. fractal_server/runner/v2/runner.py +44 -28
  176. fractal_server/runner/v2/runner_functions.py +22 -28
  177. fractal_server/runner/v2/submit_workflow.py +29 -15
  178. fractal_server/ssh/_fabric.py +6 -13
  179. fractal_server/string_tools.py +0 -1
  180. fractal_server/syringe.py +1 -1
  181. fractal_server/tasks/config/_pixi.py +1 -1
  182. fractal_server/tasks/config/_python.py +16 -9
  183. fractal_server/tasks/utils.py +0 -1
  184. fractal_server/tasks/v2/local/_utils.py +3 -3
  185. fractal_server/tasks/v2/local/collect.py +15 -18
  186. fractal_server/tasks/v2/local/collect_pixi.py +14 -16
  187. fractal_server/tasks/v2/local/deactivate.py +14 -15
  188. fractal_server/tasks/v2/local/deactivate_pixi.py +7 -7
  189. fractal_server/tasks/v2/local/delete.py +6 -8
  190. fractal_server/tasks/v2/local/reactivate.py +12 -12
  191. fractal_server/tasks/v2/local/reactivate_pixi.py +12 -12
  192. fractal_server/tasks/v2/ssh/_utils.py +3 -3
  193. fractal_server/tasks/v2/ssh/collect.py +19 -24
  194. fractal_server/tasks/v2/ssh/collect_pixi.py +22 -24
  195. fractal_server/tasks/v2/ssh/deactivate.py +17 -15
  196. fractal_server/tasks/v2/ssh/deactivate_pixi.py +8 -7
  197. fractal_server/tasks/v2/ssh/delete.py +12 -10
  198. fractal_server/tasks/v2/ssh/reactivate.py +16 -16
  199. fractal_server/tasks/v2/ssh/reactivate_pixi.py +13 -14
  200. fractal_server/tasks/v2/templates/1_create_venv.sh +2 -0
  201. fractal_server/tasks/v2/templates/2_pip_install.sh +2 -0
  202. fractal_server/tasks/v2/templates/3_pip_freeze.sh +2 -0
  203. fractal_server/tasks/v2/templates/4_pip_show.sh +2 -0
  204. fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +3 -1
  205. fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +2 -0
  206. fractal_server/tasks/v2/templates/pixi_1_extract.sh +2 -0
  207. fractal_server/tasks/v2/templates/pixi_2_install.sh +2 -0
  208. fractal_server/tasks/v2/templates/pixi_3_post_install.sh +2 -0
  209. fractal_server/tasks/v2/utils_background.py +10 -10
  210. fractal_server/tasks/v2/utils_database.py +5 -5
  211. fractal_server/tasks/v2/utils_package_names.py +1 -2
  212. fractal_server/tasks/v2/utils_pixi.py +1 -3
  213. fractal_server/types/__init__.py +98 -1
  214. fractal_server/types/validators/__init__.py +3 -0
  215. fractal_server/types/validators/_common_validators.py +33 -3
  216. fractal_server/types/validators/_workflow_task_arguments_validators.py +1 -2
  217. fractal_server/utils.py +1 -0
  218. fractal_server/zip_tools.py +34 -0
  219. {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/METADATA +3 -2
  220. fractal_server-2.18.0.dist-info/RECORD +275 -0
  221. fractal_server/app/routes/admin/v2/project.py +0 -41
  222. fractal_server-2.17.1a1.dist-info/RECORD +0 -264
  223. {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/WHEEL +0 -0
  224. {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/entry_points.txt +0 -0
  225. {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,29 +2,30 @@ import time
2
2
  from pathlib import Path
3
3
  from tempfile import TemporaryDirectory
4
4
 
5
- from ..utils_background import add_commit_refresh
6
- from ..utils_background import fail_and_cleanup
7
- from ..utils_background import get_activity_and_task_group
8
- from ..utils_templates import get_collection_replacements
9
- from ._utils import _customize_and_run_template
10
- from ._utils import check_ssh_or_fail_and_cleanup
11
5
  from fractal_server.app.db import get_sync_db
12
6
  from fractal_server.app.models import Profile
13
7
  from fractal_server.app.models import Resource
14
- from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
15
- from fractal_server.app.schemas.v2.task_group import TaskGroupActivityStatusV2
8
+ from fractal_server.app.schemas.v2 import TaskGroupActivityAction
9
+ from fractal_server.app.schemas.v2.task_group import TaskGroupActivityStatus
16
10
  from fractal_server.logger import reset_logger_handlers
17
11
  from fractal_server.logger import set_logger
18
12
  from fractal_server.ssh._fabric import SingleUseFractalSSH
19
13
  from fractal_server.ssh._fabric import SSHConfig
20
14
  from fractal_server.tasks.utils import get_log_path
15
+ from fractal_server.tasks.v2.utils_background import add_commit_refresh
16
+ from fractal_server.tasks.v2.utils_background import fail_and_cleanup
17
+ from fractal_server.tasks.v2.utils_background import get_activity_and_task_group
21
18
  from fractal_server.tasks.v2.utils_background import get_current_log
22
19
  from fractal_server.tasks.v2.utils_python_interpreter import (
23
20
  get_python_interpreter,
24
21
  )
25
22
  from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
23
+ from fractal_server.tasks.v2.utils_templates import get_collection_replacements
26
24
  from fractal_server.utils import get_timestamp
27
25
 
26
+ from ._utils import _customize_and_run_template
27
+ from ._utils import check_ssh_or_fail_and_cleanup
28
+
28
29
 
29
30
  def reactivate_ssh(
30
31
  *,
@@ -40,9 +41,10 @@ def reactivate_ssh(
40
41
  handled.
41
42
 
42
43
  Args:
43
- task_group_id:
44
44
  task_group_activity_id:
45
- ssh_config:
45
+ task_group_id:
46
+ resource:
47
+ profile:
46
48
  """
47
49
 
48
50
  LOGGER_NAME = f"{__name__}.ID{task_group_activity_id}"
@@ -101,7 +103,7 @@ def reactivate_ssh(
101
103
  )
102
104
  return
103
105
 
104
- activity.status = TaskGroupActivityStatusV2.ONGOING
106
+ activity.status = TaskGroupActivityStatus.ONGOING
105
107
  activity = add_commit_refresh(obj=activity, db=db)
106
108
 
107
109
  # Prepare replacements for templates
@@ -145,7 +147,7 @@ def reactivate_ssh(
145
147
  script_dir_remote=script_dir_remote,
146
148
  prefix=(
147
149
  f"{int(time.time())}_"
148
- f"{TaskGroupActivityActionV2.REACTIVATE}"
150
+ f"{TaskGroupActivityAction.REACTIVATE}"
149
151
  ),
150
152
  fractal_ssh=fractal_ssh,
151
153
  logger_name=LOGGER_NAME,
@@ -170,7 +172,7 @@ def reactivate_ssh(
170
172
  )
171
173
  logger.info("end - install from pip freeze")
172
174
  activity.log = get_current_log(log_file_path)
173
- activity.status = TaskGroupActivityStatusV2.OK
175
+ activity.status = TaskGroupActivityStatus.OK
174
176
  activity.timestamp_ended = get_timestamp()
175
177
  activity = add_commit_refresh(obj=activity, db=db)
176
178
  task_group.active = True
@@ -182,9 +184,7 @@ def reactivate_ssh(
182
184
  except Exception as reactivate_e:
183
185
  # Delete corrupted venv_path
184
186
  try:
185
- logger.info(
186
- f"Now delete folder {task_group.venv_path}"
187
- )
187
+ logger.info(f"Now delete folder {task_group.venv_path}")
188
188
  fractal_ssh.remove_folder(
189
189
  folder=task_group.venv_path,
190
190
  safe_root=profile.tasks_remote_dir,
@@ -2,17 +2,11 @@ import time
2
2
  from pathlib import Path
3
3
  from tempfile import TemporaryDirectory
4
4
 
5
- from ..utils_background import fail_and_cleanup
6
- from ..utils_background import get_activity_and_task_group
7
- from ..utils_pixi import SOURCE_DIR_NAME
8
- from ._pixi_slurm_ssh import run_script_on_remote_slurm
9
- from ._utils import check_ssh_or_fail_and_cleanup
10
- from ._utils import edit_pyproject_toml_in_place_ssh
11
5
  from fractal_server.app.db import get_sync_db
12
6
  from fractal_server.app.models import Profile
13
7
  from fractal_server.app.models import Resource
14
- from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
15
- from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
8
+ from fractal_server.app.schemas.v2 import TaskGroupActivityAction
9
+ from fractal_server.app.schemas.v2 import TaskGroupActivityStatus
16
10
  from fractal_server.logger import reset_logger_handlers
17
11
  from fractal_server.logger import set_logger
18
12
  from fractal_server.ssh._fabric import SingleUseFractalSSH
@@ -21,10 +15,17 @@ from fractal_server.tasks.utils import get_log_path
21
15
  from fractal_server.tasks.v2.ssh._utils import _customize_and_run_template
22
16
  from fractal_server.tasks.v2.ssh._utils import _customize_and_send_template
23
17
  from fractal_server.tasks.v2.utils_background import add_commit_refresh
18
+ from fractal_server.tasks.v2.utils_background import fail_and_cleanup
19
+ from fractal_server.tasks.v2.utils_background import get_activity_and_task_group
24
20
  from fractal_server.tasks.v2.utils_background import get_current_log
21
+ from fractal_server.tasks.v2.utils_pixi import SOURCE_DIR_NAME
25
22
  from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
26
23
  from fractal_server.utils import get_timestamp
27
24
 
25
+ from ._pixi_slurm_ssh import run_script_on_remote_slurm
26
+ from ._utils import check_ssh_or_fail_and_cleanup
27
+ from ._utils import edit_pyproject_toml_in_place_ssh
28
+
28
29
 
29
30
  def reactivate_ssh_pixi(
30
31
  *,
@@ -149,7 +150,7 @@ def reactivate_ssh_pixi(
149
150
  logger.info("installing - START")
150
151
 
151
152
  # Set status to ONGOING and refresh logs
152
- activity.status = TaskGroupActivityStatusV2.ONGOING
153
+ activity.status = TaskGroupActivityStatus.ONGOING
153
154
  activity.log = get_current_log(log_file_path)
154
155
  activity = add_commit_refresh(obj=activity, db=db)
155
156
 
@@ -163,7 +164,7 @@ def reactivate_ssh_pixi(
163
164
  script_dir_remote=script_dir_remote,
164
165
  prefix=(
165
166
  f"{int(time.time())}_"
166
- f"{TaskGroupActivityActionV2.REACTIVATE}"
167
+ f"{TaskGroupActivityAction.REACTIVATE}"
167
168
  ),
168
169
  logger_name=LOGGER_NAME,
169
170
  fractal_ssh=fractal_ssh,
@@ -233,9 +234,7 @@ def reactivate_ssh_pixi(
233
234
  remote_script3_path,
234
235
  f"chmod -R 755 {source_dir}",
235
236
  ],
236
- slurm_config=resource.tasks_pixi_config[
237
- "SLURM_CONFIG"
238
- ],
237
+ slurm_config=resource.tasks_pixi_config["SLURM_CONFIG"],
239
238
  fractal_ssh=fractal_ssh,
240
239
  logger_name=LOGGER_NAME,
241
240
  prefix=common_args["prefix"],
@@ -248,7 +247,7 @@ def reactivate_ssh_pixi(
248
247
  activity = add_commit_refresh(obj=activity, db=db)
249
248
 
250
249
  # Finalize (write metadata to DB)
251
- activity.status = TaskGroupActivityStatusV2.OK
250
+ activity.status = TaskGroupActivityStatus.OK
252
251
  activity.timestamp_ended = get_timestamp()
253
252
  activity = add_commit_refresh(obj=activity, db=db)
254
253
  task_group.active = True
@@ -1,3 +1,5 @@
1
+ #!/bin/bash
2
+
1
3
  set -e
2
4
 
3
5
  write_log(){
@@ -1,3 +1,5 @@
1
+ #!/bin/bash
2
+
1
3
  set -e
2
4
 
3
5
  write_log(){
@@ -1,3 +1,5 @@
1
+ #!/bin/bash
2
+
1
3
  set -e
2
4
 
3
5
  # Variables to be filled within fractal-server
@@ -1,3 +1,5 @@
1
+ #!/bin/bash
2
+
1
3
  set -e
2
4
 
3
5
  write_log(){
@@ -1,3 +1,5 @@
1
+ #!/bin/bash
2
+
1
3
  set -e
2
4
 
3
5
  # Variables to be filled within fractal-server
@@ -7,4 +9,4 @@ PACKAGE_ENV_DIR=__PACKAGE_ENV_DIR__
7
9
  ENV_DISK_USAGE=$(du -sk "${PACKAGE_ENV_DIR}" | cut -f1)
8
10
  ENV_FILE_NUMBER=$(find "${PACKAGE_ENV_DIR}" -type f | wc -l)
9
11
 
10
- echo $ENV_DISK_USAGE $ENV_FILE_NUMBER
12
+ echo "$ENV_DISK_USAGE" "$ENV_FILE_NUMBER"
@@ -1,3 +1,5 @@
1
+ #!/bin/bash
2
+
1
3
  set -e
2
4
 
3
5
  write_log(){
@@ -1,3 +1,5 @@
1
+ #!/bin/bash
2
+
1
3
  set -e
2
4
 
3
5
  write_log(){
@@ -1,3 +1,5 @@
1
+ #!/bin/bash
2
+
1
3
  set -e
2
4
 
3
5
  write_log(){
@@ -1,3 +1,5 @@
1
+ #!/bin/bash
2
+
1
3
  set -e
2
4
 
3
5
  write_log(){
@@ -6,10 +6,10 @@ from sqlalchemy.orm import Session as DBSyncSession
6
6
 
7
7
  from fractal_server.app.models.v2 import TaskGroupActivityV2
8
8
  from fractal_server.app.models.v2 import TaskGroupV2
9
- from fractal_server.app.schemas.v2 import TaskCreateV2
10
- from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
9
+ from fractal_server.app.schemas.v2 import TaskCreate
10
+ from fractal_server.app.schemas.v2 import TaskGroupActivityStatus
11
11
  from fractal_server.app.schemas.v2.manifest import ManifestV2
12
- from fractal_server.app.schemas.v2.task_group import TaskGroupActivityActionV2
12
+ from fractal_server.app.schemas.v2.task_group import TaskGroupActivityAction
13
13
  from fractal_server.exceptions import UnreachableBranchError
14
14
  from fractal_server.logger import get_logger
15
15
  from fractal_server.logger import reset_logger_handlers
@@ -66,11 +66,11 @@ def fail_and_cleanup(
66
66
  f"Original error: {str(exception)}"
67
67
  )
68
68
 
69
- task_group_activity.status = TaskGroupActivityStatusV2.FAILED
69
+ task_group_activity.status = TaskGroupActivityStatus.FAILED
70
70
  task_group_activity.timestamp_ended = get_timestamp()
71
71
  task_group_activity.log = get_current_log(log_file_path)
72
72
  task_group_activity = add_commit_refresh(obj=task_group_activity, db=db)
73
- if task_group_activity.action == TaskGroupActivityActionV2.COLLECT:
73
+ if task_group_activity.action == TaskGroupActivityAction.COLLECT:
74
74
  db.delete(task_group)
75
75
  db.commit()
76
76
  reset_logger_handlers(logger)
@@ -83,7 +83,7 @@ def prepare_tasks_metadata(
83
83
  python_bin: Path | None = None,
84
84
  project_python_wrapper: Path | None = None,
85
85
  package_version: str | None = None,
86
- ) -> list[TaskCreateV2]:
86
+ ) -> list[TaskCreate]:
87
87
  """
88
88
  Based on the package manifest and additional info, prepare the task list.
89
89
 
@@ -112,9 +112,9 @@ def prepare_tasks_metadata(
112
112
  if package_version is not None:
113
113
  task_attributes["version"] = package_version
114
114
  if package_manifest.has_args_schemas:
115
- task_attributes[
116
- "args_schema_version"
117
- ] = package_manifest.args_schema_version
115
+ task_attributes["args_schema_version"] = (
116
+ package_manifest.args_schema_version
117
+ )
118
118
  # Set command attributes
119
119
  if _task.executable_non_parallel is not None:
120
120
  non_parallel_path = package_root / _task.executable_non_parallel
@@ -129,7 +129,7 @@ def prepare_tasks_metadata(
129
129
  )
130
130
  task_attributes["command_parallel"] = cmd_parallel
131
131
  # Create object
132
- task_obj = TaskCreateV2(
132
+ task_obj = TaskCreate(
133
133
  **_task.model_dump(
134
134
  exclude={
135
135
  "executable_non_parallel",
@@ -3,13 +3,13 @@ from sqlalchemy.orm import Session as DBSyncSession
3
3
 
4
4
  from fractal_server.app.models.v2 import TaskGroupV2
5
5
  from fractal_server.app.models.v2 import TaskV2
6
- from fractal_server.app.schemas.v2 import TaskCreateV2
6
+ from fractal_server.app.schemas.v2 import TaskCreate
7
7
 
8
8
 
9
9
  def create_db_tasks_and_update_task_group_sync(
10
10
  *,
11
11
  task_group_id: int,
12
- task_list: list[TaskCreateV2],
12
+ task_list: list[TaskCreate],
13
13
  db: DBSyncSession,
14
14
  ) -> TaskGroupV2:
15
15
  """
@@ -17,7 +17,7 @@ def create_db_tasks_and_update_task_group_sync(
17
17
 
18
18
  Args:
19
19
  task_group_id: ID of an existing `TaskGroupV2` object.
20
- task_list: List of `TaskCreateV2` objects to be inserted into the db.
20
+ task_list: List of `TaskCreate` objects to be inserted into the db.
21
21
  db: Synchronous database session
22
22
 
23
23
  Returns:
@@ -36,7 +36,7 @@ def create_db_tasks_and_update_task_group_sync(
36
36
  async def create_db_tasks_and_update_task_group_async(
37
37
  *,
38
38
  task_group_id: int,
39
- task_list: list[TaskCreateV2],
39
+ task_list: list[TaskCreate],
40
40
  db: AsyncSession,
41
41
  ) -> TaskGroupV2:
42
42
  """
@@ -44,7 +44,7 @@ async def create_db_tasks_and_update_task_group_async(
44
44
 
45
45
  Args:
46
46
  task_group_id: ID of an existing `TaskGroupV2` object.
47
- task_list: List of `TaskCreateV2` objects to be inserted into the db.
47
+ task_list: List of `TaskCreate` objects to be inserted into the db.
48
48
  db: Synchronous database session
49
49
 
50
50
  Returns:
@@ -62,8 +62,7 @@ def compare_package_names(
62
62
  return
63
63
 
64
64
  logger.warning(
65
- f"Package name mismatch: "
66
- f"{pkg_name_task_group=}, {pkg_name_pip_show=}."
65
+ f"Package name mismatch: {pkg_name_task_group=}, {pkg_name_pip_show=}."
67
66
  )
68
67
  normalized_pkg_name_pip = normalize_package_name(pkg_name_pip_show)
69
68
  normalized_pkg_name_taskgroup = normalize_package_name(pkg_name_task_group)
@@ -95,9 +95,7 @@ def simplify_pyproject_toml(
95
95
  if key == pixi_environment
96
96
  }
97
97
  if pixi_data["environments"] == {}:
98
- raise ValueError(
99
- f"No '{pixi_environment}' pixi environment found."
100
- )
98
+ raise ValueError(f"No '{pixi_environment}' pixi environment found.")
101
99
  except KeyError:
102
100
  logger.info("KeyError for workspace/platforms - skip.")
103
101
 
@@ -6,9 +6,13 @@ from pydantic import AfterValidator
6
6
  from pydantic.types import NonNegativeInt
7
7
  from pydantic.types import StringConstraints
8
8
 
9
- from ..urls import normalize_url
9
+ from fractal_server.urls import normalize_url
10
+
10
11
  from .validators import val_absolute_path
11
12
  from .validators import val_http_url
13
+ from .validators import val_no_dotdot_in_path
14
+ from .validators import val_non_absolute_path
15
+ from .validators import val_os_path_normpath
12
16
  from .validators import val_unique_list
13
17
  from .validators import valdict_keys
14
18
  from .validators import validate_attribute_filters
@@ -18,69 +22,162 @@ NonEmptyStr = Annotated[
18
22
  str,
19
23
  StringConstraints(min_length=1, strip_whitespace=True),
20
24
  ]
25
+ """
26
+ A non-empty string, with no leading/trailing whitespaces.
27
+ """
28
+
21
29
 
22
30
  AbsolutePathStr = Annotated[
23
31
  NonEmptyStr,
24
32
  AfterValidator(val_absolute_path),
33
+ AfterValidator(val_no_dotdot_in_path),
34
+ AfterValidator(val_os_path_normpath),
25
35
  ]
36
+ """
37
+ String representing an absolute path.
38
+
39
+ Validation fails if the path is not absolute or if it contains a
40
+ parent-directory reference "/../".
41
+ """
42
+
43
+ RelativePathStr = Annotated[
44
+ NonEmptyStr,
45
+ AfterValidator(val_no_dotdot_in_path),
46
+ AfterValidator(val_os_path_normpath),
47
+ AfterValidator(val_non_absolute_path),
48
+ ]
49
+
26
50
  HttpUrlStr = Annotated[
27
51
  NonEmptyStr,
28
52
  AfterValidator(val_http_url),
29
53
  ]
54
+ """
55
+ String representing an URL.
56
+ """
57
+
58
+
30
59
  ZarrUrlStr = Annotated[
31
60
  NonEmptyStr,
61
+ AfterValidator(val_no_dotdot_in_path),
32
62
  AfterValidator(normalize_url),
33
63
  ]
64
+ """
65
+ String representing a zarr URL/path.
66
+
67
+ Validation fails if the path is not absolute or if it contains a
68
+ parent-directory reference "/../".
69
+ """
70
+
71
+
34
72
  ZarrDirStr = Annotated[
35
73
  NonEmptyStr,
74
+ AfterValidator(val_no_dotdot_in_path),
36
75
  AfterValidator(normalize_url),
37
76
  ]
77
+ """
78
+ String representing a `zarr_dir` path.
79
+
80
+ Validation fails if the path is not absolute or if it contains a
81
+ parent-directory reference "/../".
82
+ """
38
83
 
39
84
  DictStrAny = Annotated[
40
85
  dict[str, Any],
41
86
  AfterValidator(valdict_keys),
42
87
  ]
88
+ """
89
+ Dictionary where keys are strings with no leading/trailing whitespaces.
90
+ """
91
+
92
+
43
93
  DictStrStr = Annotated[
44
94
  dict[str, NonEmptyStr],
45
95
  AfterValidator(valdict_keys),
46
96
  ]
97
+ """
98
+ Dictionary where keys are strings with no leading/trailing whitespaces and
99
+ values are non-empty strings.
100
+ """
47
101
 
48
102
  ListUniqueNonEmptyString = Annotated[
49
103
  list[NonEmptyStr],
50
104
  AfterValidator(val_unique_list),
51
105
  ]
106
+ """
107
+ List of unique non-empty-string items.
108
+ """
109
+
110
+
52
111
  ListUniqueNonNegativeInt = Annotated[
53
112
  list[NonNegativeInt],
54
113
  AfterValidator(val_unique_list),
55
114
  ]
115
+ """
116
+ List of unique non-negative-integer items.
117
+ """
118
+
119
+
56
120
  ListUniqueAbsolutePathStr = Annotated[
57
121
  list[AbsolutePathStr],
58
122
  AfterValidator(val_unique_list),
59
123
  ]
124
+ """
125
+ List of unique absolute-path-string items.
126
+ """
60
127
 
61
128
  WorkflowTaskArgument = Annotated[
62
129
  DictStrAny,
63
130
  AfterValidator(validate_wft_args),
64
131
  ]
132
+ """
133
+ Dictionary with no keys from a given forbid-list.
134
+ """
65
135
 
66
136
  ImageAttributeValue = Union[int, float, str, bool]
137
+ """
138
+ Possible values for image attributes.
139
+ """
140
+
67
141
  ImageAttributes = Annotated[
68
142
  dict[str, ImageAttributeValue],
69
143
  AfterValidator(valdict_keys),
70
144
  ]
145
+ """
146
+ Image-attributes dictionary.
147
+ """
148
+
149
+
71
150
  ImageAttributesWithNone = Annotated[
72
151
  dict[str, ImageAttributeValue | None],
73
152
  AfterValidator(valdict_keys),
74
153
  ]
154
+ """
155
+ Image-attributes dictionary, including `None` attributes.
156
+ """
157
+
158
+
75
159
  AttributeFilters = Annotated[
76
160
  dict[str, list[ImageAttributeValue]],
77
161
  AfterValidator(validate_attribute_filters),
78
162
  ]
163
+ """
164
+ Image-attributes filters.
165
+ """
166
+
167
+
79
168
  TypeFilters = Annotated[
80
169
  dict[str, bool],
81
170
  AfterValidator(valdict_keys),
82
171
  ]
172
+ """
173
+ Image-type filters.
174
+ """
175
+
176
+
83
177
  ImageTypes = Annotated[
84
178
  dict[str, bool],
85
179
  AfterValidator(valdict_keys),
86
180
  ]
181
+ """
182
+ Image types.
183
+ """
@@ -1,6 +1,9 @@
1
1
  from ._common_validators import val_absolute_path # noqa F401
2
+ from ._common_validators import val_no_dotdot_in_path # noqa F401
3
+ from ._common_validators import val_os_path_normpath # noqa F401
2
4
  from ._common_validators import val_http_url # noqa F401
3
5
  from ._common_validators import val_unique_list # noqa F401
4
6
  from ._common_validators import valdict_keys # noqa F401
7
+ from ._common_validators import val_non_absolute_path # noqa F401
5
8
  from ._filter_validators import validate_attribute_filters # noqa F401
6
9
  from ._workflow_task_arguments_validators import validate_wft_args # noqa F401
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from pathlib import Path
2
3
  from typing import Any
3
4
 
4
5
  from pydantic import HttpUrl
@@ -13,9 +14,7 @@ def valdict_keys(d: dict[str, Any]) -> dict[str, Any]:
13
14
  if any(k == "" for k in new_keys):
14
15
  raise ValueError(f"Empty string in {new_keys}.")
15
16
  if len(new_keys) != len(set(new_keys)):
16
- raise ValueError(
17
- f"Dictionary contains multiple identical keys: '{d}'."
18
- )
17
+ raise ValueError(f"Dictionary contains multiple identical keys: '{d}'.")
19
18
  for old_key, new_key in zip(old_keys, new_keys):
20
19
  if new_key != old_key:
21
20
  d[new_key] = d.pop(old_key)
@@ -31,6 +30,37 @@ def val_absolute_path(path: str) -> str:
31
30
  return path
32
31
 
33
32
 
33
+ def val_non_absolute_path(path: str) -> str:
34
+ """
35
+ Check that a string attribute is not an absolute path
36
+ """
37
+ if os.path.isabs(path):
38
+ raise ValueError(
39
+ f"String must not be an absolute path (given '{path}')."
40
+ )
41
+ return path
42
+
43
+
44
+ def val_no_dotdot_in_path(path: str) -> str:
45
+ """
46
+ Check that a string attribute has no '/../' in it
47
+ """
48
+ if ".." in Path(path).parts:
49
+ raise ValueError("String must not contain '/../'.")
50
+ return path
51
+
52
+
53
+ def val_os_path_normpath(path: str) -> str:
54
+ """
55
+ Apply `os.path.normpath` to `path`.
56
+
57
+ Note: we keep this separate from `fractal_server.urls.normalize_url`,
58
+ because this function only applies to on-disk paths, while `normalize_url`
59
+ may apply to s3 URLs as well.
60
+ """
61
+ return os.path.normpath(path)
62
+
63
+
34
64
  def val_unique_list(must_be_unique: list) -> list:
35
65
  if len(set(must_be_unique)) != len(must_be_unique):
36
66
  raise ValueError("List has repetitions")
@@ -4,7 +4,6 @@ def validate_wft_args(value: dict) -> dict:
4
4
  intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
5
5
  if intersect_keys:
6
6
  raise ValueError(
7
- "`args` contains the following forbidden keys: "
8
- f"{intersect_keys}"
7
+ f"`args` contains the following forbidden keys: {intersect_keys}"
9
8
  )
10
9
  return value
fractal_server/utils.py CHANGED
@@ -13,6 +13,7 @@
13
13
  This module provides general purpose utilities that are not specific to any
14
14
  subsystem.
15
15
  """
16
+
16
17
  import shlex
17
18
  import subprocess # nosec
18
19
  from datetime import datetime
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import shutil
3
+ import subprocess # nosec
3
4
  from collections.abc import Iterator
4
5
  from io import BytesIO
5
6
  from pathlib import Path
@@ -144,3 +145,36 @@ def _zip_folder_to_file_and_remove(folder: str) -> None:
144
145
  logger.info(f"Removing folder '{folder}'.")
145
146
  shutil.rmtree(folder)
146
147
  logger.info("Folder removed.")
148
+
149
+
150
+ def _read_single_file_from_zip(*, file_path: str, archive_path: str) -> str:
151
+ """
152
+ Reads and returns the contents of a single file from a ZIP archive using
153
+ `unzip -p`.
154
+
155
+ Args:
156
+ file_path:
157
+ relative to the archive
158
+ archive_path:
159
+
160
+ Returns:
161
+ The file content
162
+
163
+ Raises:
164
+ FileNotFoundError:
165
+ if the file is not inside the archive
166
+ """
167
+ result = subprocess.run( # nosec
168
+ ["unzip", "-p", archive_path, file_path],
169
+ capture_output=True,
170
+ encoding="utf-8",
171
+ check=False,
172
+ )
173
+
174
+ if result.returncode != 0:
175
+ # The caller function should handle this error
176
+ raise FileNotFoundError(
177
+ f"File '{file_path}' not found inside archive '{archive_path}'."
178
+ )
179
+
180
+ return result.stdout