fractal-server 2.15.0a5__tar.gz → 2.15.2__tar.gz

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 (237) hide show
  1. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/PKG-INFO +1 -1
  2. fractal_server-2.15.2/fractal_server/__init__.py +1 -0
  3. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/db/__init__.py +2 -6
  4. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/security.py +2 -2
  5. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/user_settings.py +2 -2
  6. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/dataset.py +3 -3
  7. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/job.py +6 -6
  8. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/task.py +5 -4
  9. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/task_group.py +2 -2
  10. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/workflowtask.py +5 -4
  11. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/admin/v2/job.py +8 -1
  12. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/_aux_functions.py +18 -0
  13. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/workflow.py +13 -0
  14. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/workflowtask.py +10 -0
  15. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_common/_slurm_config.py +10 -0
  16. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_common/base_slurm_runner.py +85 -18
  17. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_common/get_slurm_config.py +8 -1
  18. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_common/remote.py +9 -9
  19. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/_slurm_ssh.py +0 -13
  20. fractal_server-2.15.2/fractal_server/migrations/versions/b3ffb095f973_json_to_jsonb.py +264 -0
  21. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/ssh/_fabric.py +6 -1
  22. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/local/collect_pixi.py +1 -1
  23. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/local/reactivate_pixi.py +1 -1
  24. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/ssh/collect_pixi.py +6 -3
  25. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/ssh/reactivate_pixi.py +7 -4
  26. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/templates/pixi_1_extract.sh +1 -1
  27. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/templates/pixi_2_install.sh +2 -2
  28. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/templates/pixi_3_post_install.sh +1 -1
  29. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/pyproject.toml +3 -3
  30. fractal_server-2.15.0a5/fractal_server/__init__.py +0 -1
  31. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/LICENSE +0 -0
  32. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/README.md +0 -0
  33. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/__main__.py +0 -0
  34. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/alembic.ini +0 -0
  35. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/__init__.py +0 -0
  36. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/__init__.py +0 -0
  37. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/linkusergroup.py +0 -0
  38. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/linkuserproject.py +0 -0
  39. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/__init__.py +0 -0
  40. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/accounting.py +0 -0
  41. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/history.py +0 -0
  42. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/project.py +0 -0
  43. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/models/v2/workflow.py +0 -0
  44. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/__init__.py +0 -0
  45. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/admin/__init__.py +0 -0
  46. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/admin/v2/__init__.py +0 -0
  47. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/admin/v2/accounting.py +0 -0
  48. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/admin/v2/impersonate.py +0 -0
  49. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/admin/v2/project.py +0 -0
  50. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/admin/v2/task.py +0 -0
  51. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/admin/v2/task_group.py +0 -0
  52. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/admin/v2/task_group_lifecycle.py +0 -0
  53. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/__init__.py +0 -0
  54. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/__init__.py +0 -0
  55. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/_aux_functions_history.py +0 -0
  56. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +0 -0
  57. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/_aux_functions_task_version_update.py +0 -0
  58. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/_aux_functions_tasks.py +0 -0
  59. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +0 -0
  60. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/dataset.py +0 -0
  61. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/history.py +0 -0
  62. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/images.py +0 -0
  63. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/job.py +0 -0
  64. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/pre_submission_checks.py +0 -0
  65. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/project.py +0 -0
  66. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/status_legacy.py +0 -0
  67. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/submit.py +0 -0
  68. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/task.py +0 -0
  69. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/task_collection.py +0 -0
  70. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/task_collection_custom.py +0 -0
  71. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/task_collection_pixi.py +0 -0
  72. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/task_group.py +0 -0
  73. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/task_group_lifecycle.py +0 -0
  74. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/task_version_update.py +0 -0
  75. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/api/v2/workflow_import.py +0 -0
  76. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/auth/__init__.py +0 -0
  77. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/auth/_aux_auth.py +0 -0
  78. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/auth/current_user.py +0 -0
  79. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/auth/group.py +0 -0
  80. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/auth/login.py +0 -0
  81. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/auth/oauth.py +0 -0
  82. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/auth/register.py +0 -0
  83. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/auth/router.py +0 -0
  84. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/auth/users.py +0 -0
  85. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/aux/__init__.py +0 -0
  86. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/aux/_job.py +0 -0
  87. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/aux/_runner.py +0 -0
  88. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/aux/validate_user_settings.py +0 -0
  89. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/routes/pagination.py +0 -0
  90. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/__init__.py +0 -0
  91. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/components.py +0 -0
  92. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/exceptions.py +0 -0
  93. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/__init__.py +0 -0
  94. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/base_runner.py +0 -0
  95. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/call_command_wrapper.py +0 -0
  96. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/local/__init__.py +0 -0
  97. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/local/get_local_config.py +0 -0
  98. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/local/runner.py +0 -0
  99. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_common/__init__.py +0 -0
  100. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_common/_batching.py +0 -0
  101. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_common/_job_states.py +0 -0
  102. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_common/slurm_job_task_models.py +0 -0
  103. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_ssh/__init__.py +0 -0
  104. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_ssh/run_subprocess.py +0 -0
  105. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_ssh/runner.py +0 -0
  106. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_ssh/tar_commands.py +0 -0
  107. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_sudo/__init__.py +0 -0
  108. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_sudo/_subprocess_run_as_user.py +0 -0
  109. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/executors/slurm_sudo/runner.py +0 -0
  110. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/filenames.py +0 -0
  111. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/set_start_and_last_task_index.py +0 -0
  112. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/shutdown.py +0 -0
  113. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/task_files.py +0 -0
  114. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/__init__.py +0 -0
  115. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/_local.py +0 -0
  116. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/_slurm_sudo.py +0 -0
  117. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/db_tools.py +0 -0
  118. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/deduplicate_list.py +0 -0
  119. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/merge_outputs.py +0 -0
  120. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/runner.py +0 -0
  121. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/runner_functions.py +0 -0
  122. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/submit_workflow.py +0 -0
  123. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/v2/task_interface.py +0 -0
  124. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/runner/versions.py +0 -0
  125. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/__init__.py +0 -0
  126. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/user.py +0 -0
  127. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/user_group.py +0 -0
  128. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/user_settings.py +0 -0
  129. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/__init__.py +0 -0
  130. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/accounting.py +0 -0
  131. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/dataset.py +0 -0
  132. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/dumps.py +0 -0
  133. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/history.py +0 -0
  134. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/job.py +0 -0
  135. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/manifest.py +0 -0
  136. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/project.py +0 -0
  137. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/status_legacy.py +0 -0
  138. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/task.py +0 -0
  139. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/task_collection.py +0 -0
  140. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/task_group.py +0 -0
  141. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/workflow.py +0 -0
  142. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/schemas/v2/workflowtask.py +0 -0
  143. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/security/__init__.py +0 -0
  144. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/security/signup_email.py +0 -0
  145. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/app/user_settings.py +0 -0
  146. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/config.py +0 -0
  147. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/data_migrations/2_14_10.py +0 -0
  148. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/data_migrations/README.md +0 -0
  149. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/data_migrations/tools.py +0 -0
  150. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/exceptions.py +0 -0
  151. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/gunicorn_fractal.py +0 -0
  152. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/images/__init__.py +0 -0
  153. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/images/models.py +0 -0
  154. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/images/status_tools.py +0 -0
  155. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/images/tools.py +0 -0
  156. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/logger.py +0 -0
  157. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/main.py +0 -0
  158. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/env.py +0 -0
  159. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/naming_convention.py +0 -0
  160. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/034a469ec2eb_task_groups.py +0 -0
  161. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
  162. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +0 -0
  163. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +0 -0
  164. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/316140ff7ee1_remove_usersettings_cache_dir.py +0 -0
  165. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +0 -0
  166. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
  167. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
  168. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +0 -0
  169. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  170. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
  171. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  172. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
  173. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/791ce783d3d8_add_indices.py +0 -0
  174. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  175. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +0 -0
  176. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
  177. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +0 -0
  178. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/969d84257cac_add_historyrun_task_id.py +0 -0
  179. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
  180. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  181. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +0 -0
  182. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +0 -0
  183. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
  184. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  185. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +0 -0
  186. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +0 -0
  187. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/b1e7f7a1ff71_task_group_for_pixi.py +0 -0
  188. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/c90a7c76e996_job_id_in_history_run.py +0 -0
  189. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +0 -0
  190. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
  191. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +0 -0
  192. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +0 -0
  193. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  194. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +0 -0
  195. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
  196. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/f37aceb45062_make_historyunit_logfile_required.py +0 -0
  197. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  198. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +0 -0
  199. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/py.typed +0 -0
  200. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/ssh/__init__.py +0 -0
  201. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/string_tools.py +0 -0
  202. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/syringe.py +0 -0
  203. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/__init__.py +0 -0
  204. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/utils.py +0 -0
  205. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/__init__.py +0 -0
  206. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/local/__init__.py +0 -0
  207. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/local/_utils.py +0 -0
  208. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/local/collect.py +0 -0
  209. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/local/deactivate.py +0 -0
  210. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/local/deactivate_pixi.py +0 -0
  211. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/local/reactivate.py +0 -0
  212. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/ssh/__init__.py +0 -0
  213. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/ssh/_utils.py +0 -0
  214. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/ssh/collect.py +0 -0
  215. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/ssh/deactivate.py +0 -0
  216. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/ssh/deactivate_pixi.py +0 -0
  217. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/ssh/reactivate.py +0 -0
  218. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/templates/1_create_venv.sh +0 -0
  219. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/templates/2_pip_install.sh +0 -0
  220. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/templates/3_pip_freeze.sh +0 -0
  221. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/templates/4_pip_show.sh +0 -0
  222. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +0 -0
  223. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +0 -0
  224. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/utils_background.py +0 -0
  225. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/utils_database.py +0 -0
  226. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/utils_package_names.py +0 -0
  227. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/utils_pixi.py +0 -0
  228. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/utils_python_interpreter.py +0 -0
  229. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/tasks/v2/utils_templates.py +0 -0
  230. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/types/__init__.py +0 -0
  231. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/types/validators/__init__.py +0 -0
  232. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/types/validators/_common_validators.py +0 -0
  233. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/types/validators/_filter_validators.py +0 -0
  234. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/types/validators/_workflow_task_arguments_validators.py +0 -0
  235. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/urls.py +0 -0
  236. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/utils.py +0 -0
  237. {fractal_server-2.15.0a5 → fractal_server-2.15.2}/fractal_server/zip_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fractal-server
3
- Version: 2.15.0a5
3
+ Version: 2.15.2
4
4
  Summary: Backend component of the Fractal analytics platform
5
5
  License: BSD-3-Clause
6
6
  Author: Tommaso Comparin
@@ -0,0 +1 @@
1
+ __VERSION__ = "2.15.2"
@@ -45,13 +45,11 @@ class DB:
45
45
  settings = Inject(get_settings)
46
46
  settings.check_db()
47
47
 
48
- engine_kwargs_async = {"pool_pre_ping": True}
49
-
50
48
  cls._engine_async = create_async_engine(
51
49
  settings.DATABASE_ASYNC_URL,
52
50
  echo=settings.DB_ECHO,
53
51
  future=True,
54
- **engine_kwargs_async,
52
+ pool_pre_ping=True,
55
53
  )
56
54
  cls._async_session_maker = sessionmaker(
57
55
  cls._engine_async,
@@ -65,13 +63,11 @@ class DB:
65
63
  settings = Inject(get_settings)
66
64
  settings.check_db()
67
65
 
68
- engine_kwargs_sync = {}
69
-
70
66
  cls._engine_sync = create_engine(
71
67
  settings.DATABASE_SYNC_URL,
72
68
  echo=settings.DB_ECHO,
73
69
  future=True,
74
- **engine_kwargs_sync,
70
+ pool_pre_ping=True,
75
71
  )
76
72
 
77
73
  cls._sync_session_maker = sessionmaker(
@@ -15,8 +15,8 @@ from typing import Optional
15
15
  from pydantic import ConfigDict
16
16
  from pydantic import EmailStr
17
17
  from sqlalchemy import Column
18
+ from sqlalchemy.dialects.postgresql import JSONB
18
19
  from sqlalchemy.types import DateTime
19
- from sqlalchemy.types import JSON
20
20
  from sqlmodel import Field
21
21
  from sqlmodel import Relationship
22
22
  from sqlmodel import SQLModel
@@ -124,5 +124,5 @@ class UserGroup(SQLModel, table=True):
124
124
  sa_column=Column(DateTime(timezone=True), nullable=False),
125
125
  )
126
126
  viewer_paths: list[str] = Field(
127
- sa_column=Column(JSON, server_default="[]", nullable=False)
127
+ sa_column=Column(JSONB, server_default="[]", nullable=False)
128
128
  )
@@ -1,5 +1,5 @@
1
1
  from sqlalchemy import Column
2
- from sqlalchemy.types import JSON
2
+ from sqlalchemy.dialects.postgresql import JSONB
3
3
  from sqlmodel import Field
4
4
  from sqlmodel import SQLModel
5
5
 
@@ -25,7 +25,7 @@ class UserSettings(SQLModel, table=True):
25
25
 
26
26
  id: int | None = Field(default=None, primary_key=True)
27
27
  slurm_accounts: list[str] = Field(
28
- sa_column=Column(JSON, server_default="[]", nullable=False)
28
+ sa_column=Column(JSONB, server_default="[]", nullable=False)
29
29
  )
30
30
  ssh_host: str | None = None
31
31
  ssh_username: str | None = None
@@ -3,8 +3,8 @@ from typing import Any
3
3
 
4
4
  from pydantic import ConfigDict
5
5
  from sqlalchemy import Column
6
+ from sqlalchemy.dialects.postgresql import JSONB
6
7
  from sqlalchemy.types import DateTime
7
- from sqlalchemy.types import JSON
8
8
  from sqlmodel import Field
9
9
  from sqlmodel import Relationship
10
10
  from sqlmodel import SQLModel
@@ -24,7 +24,7 @@ class DatasetV2(SQLModel, table=True):
24
24
  )
25
25
 
26
26
  history: list[dict[str, Any]] = Field(
27
- sa_column=Column(JSON, server_default="[]", nullable=False)
27
+ sa_column=Column(JSONB, server_default="[]", nullable=False)
28
28
  )
29
29
 
30
30
  timestamp_created: datetime = Field(
@@ -34,7 +34,7 @@ class DatasetV2(SQLModel, table=True):
34
34
 
35
35
  zarr_dir: str
36
36
  images: list[dict[str, Any]] = Field(
37
- sa_column=Column(JSON, server_default="[]", nullable=False)
37
+ sa_column=Column(JSONB, server_default="[]", nullable=False)
38
38
  )
39
39
 
40
40
  @property
@@ -3,8 +3,8 @@ from typing import Any
3
3
 
4
4
  from pydantic import ConfigDict
5
5
  from sqlalchemy import Column
6
+ from sqlalchemy.dialects.postgresql import JSONB
6
7
  from sqlalchemy.types import DateTime
7
- from sqlalchemy.types import JSON
8
8
  from sqlmodel import Field
9
9
  from sqlmodel import SQLModel
10
10
 
@@ -31,13 +31,13 @@ class JobV2(SQLModel, table=True):
31
31
  slurm_account: str | None = None
32
32
 
33
33
  dataset_dump: dict[str, Any] = Field(
34
- sa_column=Column(JSON, nullable=False)
34
+ sa_column=Column(JSONB, nullable=False)
35
35
  )
36
36
  workflow_dump: dict[str, Any] = Field(
37
- sa_column=Column(JSON, nullable=False)
37
+ sa_column=Column(JSONB, nullable=False)
38
38
  )
39
39
  project_dump: dict[str, Any] = Field(
40
- sa_column=Column(JSON, nullable=False)
40
+ sa_column=Column(JSONB, nullable=False)
41
41
  )
42
42
 
43
43
  worker_init: str | None = None
@@ -57,8 +57,8 @@ class JobV2(SQLModel, table=True):
57
57
  log: str | None = None
58
58
 
59
59
  attribute_filters: AttributeFilters = Field(
60
- sa_column=Column(JSON, nullable=False, server_default="{}")
60
+ sa_column=Column(JSONB, nullable=False, server_default="{}")
61
61
  )
62
62
  type_filters: dict[str, bool] = Field(
63
- sa_column=Column(JSON, nullable=False, server_default="{}")
63
+ sa_column=Column(JSONB, nullable=False, server_default="{}")
64
64
  )
@@ -1,7 +1,8 @@
1
1
  from typing import Any
2
2
 
3
3
  from sqlalchemy import Column
4
- from sqlalchemy.types import JSON
4
+ from sqlalchemy.dialects.postgresql import JSON
5
+ from sqlalchemy.dialects.postgresql import JSONB
5
6
  from sqlmodel import Field
6
7
  from sqlmodel import SQLModel
7
8
 
@@ -33,8 +34,8 @@ class TaskV2(SQLModel, table=True):
33
34
  docs_info: str | None = None
34
35
  docs_link: str | None = None
35
36
 
36
- input_types: dict[str, bool] = Field(sa_column=Column(JSON), default={})
37
- output_types: dict[str, bool] = Field(sa_column=Column(JSON), default={})
37
+ input_types: dict[str, bool] = Field(sa_column=Column(JSONB), default={})
38
+ output_types: dict[str, bool] = Field(sa_column=Column(JSONB), default={})
38
39
 
39
40
  taskgroupv2_id: int = Field(foreign_key="taskgroupv2.id")
40
41
 
@@ -42,5 +43,5 @@ class TaskV2(SQLModel, table=True):
42
43
  modality: str | None = None
43
44
  authors: str | None = None
44
45
  tags: list[str] = Field(
45
- sa_column=Column(JSON, server_default="[]", nullable=False)
46
+ sa_column=Column(JSONB, server_default="[]", nullable=False)
46
47
  )
@@ -2,8 +2,8 @@ from datetime import datetime
2
2
  from datetime import timezone
3
3
 
4
4
  from sqlalchemy import Column
5
+ from sqlalchemy.dialects.postgresql import JSONB
5
6
  from sqlalchemy.types import DateTime
6
- from sqlalchemy.types import JSON
7
7
  from sqlmodel import Field
8
8
  from sqlmodel import Relationship
9
9
  from sqlmodel import SQLModel
@@ -35,7 +35,7 @@ class TaskGroupV2(SQLModel, table=True):
35
35
  pip_extras: str | None = None
36
36
  pinned_package_versions: dict[str, str] = Field(
37
37
  sa_column=Column(
38
- JSON,
38
+ JSONB,
39
39
  server_default="{}",
40
40
  default={},
41
41
  nullable=True,
@@ -2,7 +2,8 @@ from typing import Any
2
2
 
3
3
  from pydantic import ConfigDict
4
4
  from sqlalchemy import Column
5
- from sqlalchemy.types import JSON
5
+ from sqlalchemy.dialects.postgresql import JSON
6
+ from sqlalchemy.dialects.postgresql import JSONB
6
7
  from sqlmodel import Field
7
8
  from sqlmodel import Relationship
8
9
  from sqlmodel import SQLModel
@@ -24,14 +25,14 @@ class WorkflowTaskV2(SQLModel, table=True):
24
25
  sa_column=Column(JSON), default=None
25
26
  )
26
27
  args_parallel: dict[str, Any] | None = Field(
27
- sa_column=Column(JSON), default=None
28
+ sa_column=Column(JSONB), default=None
28
29
  )
29
30
  args_non_parallel: dict[str, Any] | None = Field(
30
- sa_column=Column(JSON), default=None
31
+ sa_column=Column(JSONB), default=None
31
32
  )
32
33
 
33
34
  type_filters: dict[str, bool] = Field(
34
- sa_column=Column(JSON, nullable=False, server_default="{}")
35
+ sa_column=Column(JSONB, nullable=False, server_default="{}")
35
36
  )
36
37
 
37
38
  # Task
@@ -156,8 +156,15 @@ async def update_job(
156
156
  detail=f"Cannot set job status to {job_update.status}",
157
157
  )
158
158
 
159
+ timestamp = get_timestamp()
159
160
  setattr(job, "status", job_update.status)
160
- setattr(job, "end_timestamp", get_timestamp())
161
+ setattr(job, "end_timestamp", timestamp)
162
+ setattr(
163
+ job,
164
+ "log",
165
+ f"{job.log or ''}\nThis job was manually marked as "
166
+ f"'{JobStatusTypeV2.FAILED}' by an admin ({timestamp.isoformat()}).",
167
+ )
161
168
  await db.commit()
162
169
  await db.refresh(job)
163
170
  await db.close()
@@ -325,6 +325,24 @@ def _get_submitted_jobs_statement() -> SelectOfScalar:
325
325
  return stm
326
326
 
327
327
 
328
+ async def _workflow_has_submitted_job(
329
+ workflow_id: int,
330
+ db: AsyncSession,
331
+ ) -> bool:
332
+
333
+ res = await db.execute(
334
+ select(JobV2.id)
335
+ .where(JobV2.status == JobStatusTypeV2.SUBMITTED)
336
+ .where(JobV2.workflow_id == workflow_id)
337
+ .limit(1)
338
+ )
339
+ submitted_jobs = res.scalar_one_or_none()
340
+ if submitted_jobs is not None:
341
+ return True
342
+
343
+ return False
344
+
345
+
328
346
  async def _workflow_insert_task(
329
347
  *,
330
348
  workflow_id: int,
@@ -22,6 +22,7 @@ from ._aux_functions import _check_workflow_exists
22
22
  from ._aux_functions import _get_project_check_owner
23
23
  from ._aux_functions import _get_submitted_jobs_statement
24
24
  from ._aux_functions import _get_workflow_check_owner
25
+ from ._aux_functions import _workflow_has_submitted_job
25
26
  from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
26
27
  from fractal_server.app.models import UserOAuth
27
28
  from fractal_server.app.models.v2 import TaskGroupV2
@@ -146,6 +147,18 @@ async def update_workflow(
146
147
 
147
148
  for key, value in patch.model_dump(exclude_unset=True).items():
148
149
  if key == "reordered_workflowtask_ids":
150
+
151
+ if await _workflow_has_submitted_job(
152
+ workflow_id=workflow_id, db=db
153
+ ):
154
+ raise HTTPException(
155
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
156
+ detail=(
157
+ "Cannot re-order WorkflowTasks while a Job is running "
158
+ "for this Workflow."
159
+ ),
160
+ )
161
+
149
162
  current_workflowtask_ids = [
150
163
  wftask.id for wftask in workflow.task_list
151
164
  ]
@@ -10,6 +10,7 @@ from ....db import AsyncSession
10
10
  from ....db import get_async_db
11
11
  from ._aux_functions import _get_workflow_check_owner
12
12
  from ._aux_functions import _get_workflow_task_check_owner
13
+ from ._aux_functions import _workflow_has_submitted_job
13
14
  from ._aux_functions import _workflow_insert_task
14
15
  from ._aux_functions_tasks import _check_type_filters_compatibility
15
16
  from ._aux_functions_tasks import _get_task_read_access
@@ -224,6 +225,15 @@ async def delete_workflowtask(
224
225
  db=db,
225
226
  )
226
227
 
228
+ if await _workflow_has_submitted_job(workflow_id=workflow_id, db=db):
229
+ raise HTTPException(
230
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
231
+ detail=(
232
+ "Cannot delete a WorkflowTask while a Job is running for this "
233
+ "Workflow."
234
+ ),
235
+ )
236
+
227
237
  # Delete WorkflowTask
228
238
  await db.delete(db_workflow_task)
229
239
  await db.commit()
@@ -48,6 +48,8 @@ class _SlurmConfigSet(BaseModel):
48
48
  constraint:
49
49
  gres:
50
50
  time:
51
+ exclude:
52
+ nodelist:
51
53
  account:
52
54
  extra_lines:
53
55
  """
@@ -59,6 +61,8 @@ class _SlurmConfigSet(BaseModel):
59
61
  mem: int | str | None = None
60
62
  constraint: str | None = None
61
63
  gres: str | None = None
64
+ exclude: str | None = None
65
+ nodelist: str | None = None
62
66
  time: str | None = None
63
67
  account: str | None = None
64
68
  extra_lines: list[str] | None = None
@@ -227,6 +231,8 @@ class SlurmConfig(BaseModel):
227
231
  account: Corresponds to SLURM option.
228
232
  gpus: Corresponds to SLURM option.
229
233
  time: Corresponds to SLURM option (WARNING: not fully supported).
234
+ nodelist: Corresponds to SLURM option.
235
+ exclude: Corresponds to SLURM option.
230
236
  prefix: Prefix of configuration lines in SLURM submission scripts.
231
237
  shebang_line: Shebang line for SLURM submission scripts.
232
238
  extra_lines: Additional lines to include in SLURM submission scripts.
@@ -268,6 +274,8 @@ class SlurmConfig(BaseModel):
268
274
  gpus: str | None = None
269
275
  time: str | None = None
270
276
  account: str | None = None
277
+ nodelist: str | None = None
278
+ exclude: str | None = None
271
279
 
272
280
  # Free-field attribute for extra lines to be added to the SLURM job
273
281
  # preamble
@@ -361,6 +369,8 @@ class SlurmConfig(BaseModel):
361
369
  "gpus",
362
370
  "time",
363
371
  "account",
372
+ "exclude",
373
+ "nodelist",
364
374
  ]:
365
375
  value = getattr(self, key)
366
376
  if value is not None:
@@ -137,6 +137,34 @@ class BaseSlurmRunner(BaseRunner):
137
137
  def run_squeue(self, *, job_ids: list[str], **kwargs) -> str:
138
138
  raise NotImplementedError("Implement in child class.")
139
139
 
140
+ def _is_squeue_error_recoverable(self, exception: BaseException) -> True:
141
+ """
142
+ Determine whether a `squeue` error is considered recoverable.
143
+
144
+ A _recoverable_ error is one which will disappear after some time,
145
+ without any specific action from the `fractal-server` side.
146
+
147
+ Note: if this function returns `True` for an error that does not
148
+ actually recover, this leads to an infinite loop where
149
+ `fractal-server` keeps polling `squeue` information forever.
150
+
151
+ More info at
152
+ https://github.com/fractal-analytics-platform/fractal-server/issues/2682
153
+
154
+ Args:
155
+ exception: The exception raised by `self.run_squeue`.
156
+ Returns:
157
+ Whether the error is considered recoverable.
158
+ """
159
+ str_exception = str(exception)
160
+ if (
161
+ "slurm_load_jobs" in str_exception
162
+ and "Socket timed out on send/recv operation" in str_exception
163
+ ):
164
+ return True
165
+ else:
166
+ return False
167
+
140
168
  def _get_finished_jobs(self, job_ids: list[str]) -> set[str]:
141
169
  # If there is no Slurm job to check, return right away
142
170
  if not job_ids:
@@ -161,12 +189,26 @@ class BaseSlurmRunner(BaseRunner):
161
189
  {stdout.split()[0]: stdout.split()[1]}
162
190
  )
163
191
  except Exception as e:
164
- logger.warning(
165
- "[_get_finished_jobs] `squeue` failed for "
166
- f"{job_id=}, mark job as completed. "
192
+ msg = (
193
+ f"[_get_finished_jobs] `squeue` failed for {job_id=}. "
167
194
  f"Original error: {str(e)}."
168
195
  )
169
- slurm_statuses.update({str(job_id): "COMPLETED"})
196
+ logger.warning(msg)
197
+ if self._is_squeue_error_recoverable(e):
198
+ logger.warning(
199
+ "[_get_finished_jobs] Recoverable `squeue` "
200
+ f"error - mark {job_id=} as FRACTAL_UNDEFINED and"
201
+ " retry later."
202
+ )
203
+ slurm_statuses.update(
204
+ {str(job_id): "FRACTAL_UNDEFINED"}
205
+ )
206
+ else:
207
+ logger.warning(
208
+ "[_get_finished_jobs] Non-recoverable `squeue`"
209
+ f"error - mark {job_id=} as completed."
210
+ )
211
+ slurm_statuses.update({str(job_id): "COMPLETED"})
170
212
 
171
213
  # If a job is not in `squeue` output, mark it as completed.
172
214
  finished_jobs = {
@@ -182,33 +224,53 @@ class BaseSlurmRunner(BaseRunner):
182
224
  def _mkdir_remote_folder(self, folder: str) -> None:
183
225
  raise NotImplementedError("Implement in child class.")
184
226
 
185
- def _submit_single_sbatch(
227
+ def _enrich_slurm_config(
186
228
  self,
187
- *,
188
- base_command: str,
189
- slurm_job: SlurmJob,
190
229
  slurm_config: SlurmConfig,
191
- ) -> str:
192
- logger.debug("[_submit_single_sbatch] START")
230
+ ) -> SlurmConfig:
231
+ """
232
+ Return an enriched `SlurmConfig` object
193
233
 
194
- # Include SLURM account in `slurm_config`. Note: we make this change
195
- # here, rather than exposing a new argument of `get_slurm_config`,
196
- # because it's a backend-specific argument while `get_slurm_config` has
197
- # a generic interface.
234
+ Include `self.account` and `self.common_script_lines` into a
235
+ `SlurmConfig` object. Extracting this logic into an independent
236
+ class method is useful to fix issue #2659 (which was due to
237
+ performing this same operation multiple times rather than once).
238
+
239
+ Args:
240
+ slurm_config: The original `SlurmConfig` object.
241
+
242
+ Returns:
243
+ A new, up-to-date, `SlurmConfig` object.
244
+ """
245
+
246
+ new_slurm_config = slurm_config.model_copy()
247
+
248
+ # Include SLURM account in `slurm_config`.
198
249
  if self.slurm_account is not None:
199
- slurm_config.account = self.slurm_account
250
+ new_slurm_config.account = self.slurm_account
200
251
 
201
252
  # Include common_script_lines in extra_lines
202
253
  if len(self.common_script_lines) > 0:
203
254
  logger.debug(
204
255
  f"Add {self.common_script_lines} to "
205
- f"{slurm_config.extra_lines=}."
256
+ f"{new_slurm_config.extra_lines=}."
206
257
  )
207
- current_extra_lines = slurm_config.extra_lines or []
208
- slurm_config.extra_lines = (
258
+ current_extra_lines = new_slurm_config.extra_lines or []
259
+ new_slurm_config.extra_lines = (
209
260
  current_extra_lines + self.common_script_lines
210
261
  )
211
262
 
263
+ return new_slurm_config
264
+
265
+ def _submit_single_sbatch(
266
+ self,
267
+ *,
268
+ base_command: str,
269
+ slurm_job: SlurmJob,
270
+ slurm_config: SlurmConfig,
271
+ ) -> str:
272
+ logger.debug("[_submit_single_sbatch] START")
273
+
212
274
  for task in slurm_job.tasks:
213
275
  # Write input file
214
276
  if self.slurm_runner_type == "ssh":
@@ -508,6 +570,9 @@ class BaseSlurmRunner(BaseRunner):
508
570
  user_id: int,
509
571
  ) -> tuple[Any, Exception]:
510
572
  logger.debug("[submit] START")
573
+
574
+ config = self._enrich_slurm_config(config)
575
+
511
576
  try:
512
577
  workdir_local = task_files.wftask_subfolder_local
513
578
  workdir_remote = task_files.wftask_subfolder_remote
@@ -649,6 +714,8 @@ class BaseSlurmRunner(BaseRunner):
649
714
  input images, while for compound tasks these can differ.
650
715
  """
651
716
 
717
+ config = self._enrich_slurm_config(config)
718
+
652
719
  logger.debug(f"[multisubmit] START, {len(list_parameters)=}")
653
720
  try:
654
721
  if self.is_shutdown():
@@ -125,7 +125,14 @@ def get_slurm_config_internal(
125
125
  )
126
126
  logger.error(error_msg)
127
127
  raise SlurmConfigError(error_msg)
128
- for key in ["time", "gres", "gpus", "constraint"]:
128
+ for key in [
129
+ "time",
130
+ "gres",
131
+ "gpus",
132
+ "constraint",
133
+ "nodelist",
134
+ "exclude",
135
+ ]:
129
136
  value = wftask_meta.get(key, None)
130
137
  if value is not None:
131
138
  slurm_dict[key] = value
@@ -1,6 +1,5 @@
1
1
  import argparse
2
2
  import json
3
- import logging
4
3
  import os
5
4
  import sys
6
5
 
@@ -32,7 +31,6 @@ def worker(
32
31
  # Create output folder, if missing
33
32
  out_dir = os.path.dirname(out_fname)
34
33
  if not os.path.exists(out_dir):
35
- logging.debug(f"_slurm.remote.worker: create {out_dir=}")
36
34
  os.mkdir(out_dir)
37
35
 
38
36
  # Execute the job and capture exceptions
@@ -40,10 +38,8 @@ def worker(
40
38
  with open(in_fname) as f:
41
39
  input_data = json.load(f)
42
40
 
43
- server_python_version = input_data["python_version"]
44
- server_fractal_server_version = input_data["fractal_server_version"]
45
-
46
41
  # Fractal-server version must be identical
42
+ server_fractal_server_version = input_data["fractal_server_version"]
47
43
  worker_fractal_server_version = __VERSION__
48
44
  if worker_fractal_server_version != server_fractal_server_version:
49
45
  raise FractalVersionMismatch(
@@ -51,11 +47,16 @@ def worker(
51
47
  f"{worker_fractal_server_version=}"
52
48
  )
53
49
 
54
- # Python version mismatch only raises a warning
55
- worker_python_version = tuple(sys.version_info[:3])
50
+ # Get `worker_python_version` as a `list` since this is the type of
51
+ # `server_python_version` after a JSON dump/load round trip.
52
+ worker_python_version = list(sys.version_info[:3])
53
+
54
+ # Print a warning for Python version mismatch
55
+ server_python_version = input_data["python_version"]
56
56
  if worker_python_version != server_python_version:
57
57
  if worker_python_version[:2] != server_python_version[:2]:
58
- logging.warning(
58
+ print(
59
+ "WARNING: "
59
60
  f"{server_python_version=} but {worker_python_version=}."
60
61
  )
61
62
 
@@ -116,7 +117,6 @@ if __name__ == "__main__":
116
117
  required=True,
117
118
  )
118
119
  parsed_args = parser.parse_args()
119
- logging.debug(f"{parsed_args=}")
120
120
 
121
121
  kwargs = dict(
122
122
  in_fname=parsed_args.input_file,
@@ -20,7 +20,6 @@ from pathlib import Path
20
20
  from ....ssh._fabric import FractalSSH
21
21
  from ...models.v2 import DatasetV2
22
22
  from ...models.v2 import WorkflowV2
23
- from ..exceptions import JobExecutionError
24
23
  from ..executors.slurm_common.get_slurm_config import get_slurm_config
25
24
  from ..executors.slurm_ssh.runner import SlurmSSHRunner
26
25
  from ..set_start_and_last_task_index import set_start_and_last_task_index
@@ -64,18 +63,6 @@ def process_workflow(
64
63
  if isinstance(worker_init, str):
65
64
  worker_init = worker_init.split("\n")
66
65
 
67
- # Create main remote folder
68
- try:
69
- fractal_ssh.mkdir(folder=str(workflow_dir_remote))
70
- logger.info(f"Created {str(workflow_dir_remote)} via SSH.")
71
- except Exception as e:
72
- error_msg = (
73
- f"Could not create {str(workflow_dir_remote)} via SSH.\n"
74
- f"Original error: {str(e)}."
75
- )
76
- logger.error(error_msg)
77
- raise JobExecutionError(info=error_msg)
78
-
79
66
  with SlurmSSHRunner(
80
67
  fractal_ssh=fractal_ssh,
81
68
  root_dir_local=workflow_dir_local,