fractal-server 2.14.4a0__tar.gz → 2.14.5__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 (221) hide show
  1. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/PKG-INFO +1 -7
  2. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/README.md +0 -5
  3. fractal_server-2.14.5/fractal_server/__init__.py +1 -0
  4. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/exceptions.py +1 -6
  5. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/base_runner.py +16 -4
  6. fractal_server-2.14.5/fractal_server/app/runner/executors/call_command_wrapper.py +52 -0
  7. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/local/runner.py +52 -13
  8. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_common/base_slurm_runner.py +87 -52
  9. fractal_server-2.14.5/fractal_server/app/runner/executors/slurm_common/remote.py +125 -0
  10. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_common/slurm_job_task_models.py +20 -19
  11. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_ssh/runner.py +1 -2
  12. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_sudo/_subprocess_run_as_user.py +1 -4
  13. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_sudo/runner.py +3 -11
  14. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/task_files.py +0 -8
  15. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/_slurm_ssh.py +1 -2
  16. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/_slurm_sudo.py +1 -2
  17. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/runner_functions.py +16 -30
  18. fractal_server-2.14.5/fractal_server/app/runner/versions.py +21 -0
  19. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/config.py +0 -9
  20. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/ssh/_fabric.py +4 -1
  21. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/pyproject.toml +2 -3
  22. fractal_server-2.14.4a0/fractal_server/__init__.py +0 -1
  23. fractal_server-2.14.4a0/fractal_server/app/runner/executors/slurm_common/remote.py +0 -170
  24. fractal_server-2.14.4a0/fractal_server/app/runner/executors/slurm_common/utils_executors.py +0 -58
  25. fractal_server-2.14.4a0/fractal_server/app/runner/v2/runner_functions_low_level.py +0 -122
  26. fractal_server-2.14.4a0/fractal_server/app/runner/versions.py +0 -30
  27. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/LICENSE +0 -0
  28. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/__main__.py +0 -0
  29. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/alembic.ini +0 -0
  30. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/__init__.py +0 -0
  31. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/db/__init__.py +0 -0
  32. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/history/__init__.py +0 -0
  33. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/__init__.py +0 -0
  34. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/linkusergroup.py +0 -0
  35. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/linkuserproject.py +0 -0
  36. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/security.py +0 -0
  37. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/user_settings.py +0 -0
  38. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/__init__.py +0 -0
  39. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/accounting.py +0 -0
  40. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/dataset.py +0 -0
  41. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/history.py +0 -0
  42. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/job.py +0 -0
  43. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/project.py +0 -0
  44. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/task.py +0 -0
  45. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/task_group.py +0 -0
  46. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/workflow.py +0 -0
  47. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/models/v2/workflowtask.py +0 -0
  48. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/__init__.py +0 -0
  49. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/admin/__init__.py +0 -0
  50. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/admin/v2/__init__.py +0 -0
  51. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/admin/v2/accounting.py +0 -0
  52. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/admin/v2/impersonate.py +0 -0
  53. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/admin/v2/job.py +0 -0
  54. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/admin/v2/project.py +0 -0
  55. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/admin/v2/task.py +0 -0
  56. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/admin/v2/task_group.py +0 -0
  57. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/admin/v2/task_group_lifecycle.py +0 -0
  58. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/__init__.py +0 -0
  59. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/__init__.py +0 -0
  60. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/_aux_functions.py +0 -0
  61. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/_aux_functions_history.py +0 -0
  62. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +0 -0
  63. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/_aux_functions_tasks.py +0 -0
  64. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/dataset.py +0 -0
  65. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/history.py +0 -0
  66. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/images.py +0 -0
  67. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/job.py +0 -0
  68. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/pre_submission_checks.py +0 -0
  69. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/project.py +0 -0
  70. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/status_legacy.py +0 -0
  71. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/submit.py +0 -0
  72. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/task.py +0 -0
  73. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/task_collection.py +0 -0
  74. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/task_collection_custom.py +0 -0
  75. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/task_group.py +0 -0
  76. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/task_group_lifecycle.py +0 -0
  77. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/workflow.py +0 -0
  78. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/workflow_import.py +0 -0
  79. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/api/v2/workflowtask.py +0 -0
  80. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/auth/__init__.py +0 -0
  81. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/auth/_aux_auth.py +0 -0
  82. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/auth/current_user.py +0 -0
  83. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/auth/group.py +0 -0
  84. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/auth/login.py +0 -0
  85. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/auth/oauth.py +0 -0
  86. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/auth/register.py +0 -0
  87. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/auth/router.py +0 -0
  88. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/auth/users.py +0 -0
  89. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/aux/__init__.py +0 -0
  90. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/aux/_job.py +0 -0
  91. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/aux/_runner.py +0 -0
  92. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/aux/validate_user_settings.py +0 -0
  93. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/routes/pagination.py +0 -0
  94. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/__init__.py +0 -0
  95. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/components.py +0 -0
  96. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/__init__.py +0 -0
  97. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/local/__init__.py +0 -0
  98. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/local/get_local_config.py +0 -0
  99. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_common/__init__.py +0 -0
  100. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_common/_batching.py +0 -0
  101. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_common/_job_states.py +0 -0
  102. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_common/_slurm_config.py +0 -0
  103. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_common/get_slurm_config.py +0 -0
  104. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_ssh/__init__.py +0 -0
  105. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_ssh/run_subprocess.py +0 -0
  106. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_ssh/tar_commands.py +0 -0
  107. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/executors/slurm_sudo/__init__.py +0 -0
  108. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/filenames.py +0 -0
  109. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/set_start_and_last_task_index.py +0 -0
  110. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/shutdown.py +0 -0
  111. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/__init__.py +0 -0
  112. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/_local.py +0 -0
  113. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/db_tools.py +0 -0
  114. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/deduplicate_list.py +0 -0
  115. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/merge_outputs.py +0 -0
  116. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/runner.py +0 -0
  117. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/submit_workflow.py +0 -0
  118. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/runner/v2/task_interface.py +0 -0
  119. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/__init__.py +0 -0
  120. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/user.py +0 -0
  121. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/user_group.py +0 -0
  122. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/user_settings.py +0 -0
  123. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/__init__.py +0 -0
  124. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/accounting.py +0 -0
  125. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/dataset.py +0 -0
  126. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/dumps.py +0 -0
  127. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/history.py +0 -0
  128. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/job.py +0 -0
  129. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/manifest.py +0 -0
  130. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/project.py +0 -0
  131. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/status_legacy.py +0 -0
  132. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/task.py +0 -0
  133. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/task_collection.py +0 -0
  134. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/task_group.py +0 -0
  135. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/workflow.py +0 -0
  136. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/schemas/v2/workflowtask.py +0 -0
  137. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/security/__init__.py +0 -0
  138. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/security/signup_email.py +0 -0
  139. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/app/user_settings.py +0 -0
  140. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/data_migrations/README.md +0 -0
  141. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/data_migrations/tools.py +0 -0
  142. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/gunicorn_fractal.py +0 -0
  143. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/images/__init__.py +0 -0
  144. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/images/models.py +0 -0
  145. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/images/tools.py +0 -0
  146. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/logger.py +0 -0
  147. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/main.py +0 -0
  148. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/env.py +0 -0
  149. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/naming_convention.py +0 -0
  150. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/034a469ec2eb_task_groups.py +0 -0
  151. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
  152. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +0 -0
  153. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +0 -0
  154. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/316140ff7ee1_remove_usersettings_cache_dir.py +0 -0
  155. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +0 -0
  156. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
  157. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
  158. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +0 -0
  159. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  160. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
  161. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  162. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
  163. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  164. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +0 -0
  165. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
  166. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +0 -0
  167. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
  168. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  169. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +0 -0
  170. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +0 -0
  171. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
  172. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  173. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +0 -0
  174. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +0 -0
  175. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/c90a7c76e996_job_id_in_history_run.py +0 -0
  176. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +0 -0
  177. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
  178. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +0 -0
  179. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +0 -0
  180. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  181. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +0 -0
  182. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
  183. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/f37aceb45062_make_historyunit_logfile_required.py +0 -0
  184. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  185. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +0 -0
  186. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/py.typed +0 -0
  187. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/ssh/__init__.py +0 -0
  188. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/string_tools.py +0 -0
  189. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/syringe.py +0 -0
  190. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/__init__.py +0 -0
  191. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/utils.py +0 -0
  192. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/__init__.py +0 -0
  193. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/local/__init__.py +0 -0
  194. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/local/_utils.py +0 -0
  195. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/local/collect.py +0 -0
  196. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/local/deactivate.py +0 -0
  197. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/local/reactivate.py +0 -0
  198. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/ssh/__init__.py +0 -0
  199. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/ssh/_utils.py +0 -0
  200. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/ssh/collect.py +0 -0
  201. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/ssh/deactivate.py +0 -0
  202. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/ssh/reactivate.py +0 -0
  203. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/templates/1_create_venv.sh +0 -0
  204. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/templates/2_pip_install.sh +0 -0
  205. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/templates/3_pip_freeze.sh +0 -0
  206. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/templates/4_pip_show.sh +0 -0
  207. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +0 -0
  208. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +0 -0
  209. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/utils_background.py +0 -0
  210. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/utils_database.py +0 -0
  211. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/utils_package_names.py +0 -0
  212. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/utils_python_interpreter.py +0 -0
  213. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/tasks/v2/utils_templates.py +0 -0
  214. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/types/__init__.py +0 -0
  215. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/types/validators/__init__.py +0 -0
  216. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/types/validators/_common_validators.py +0 -0
  217. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/types/validators/_filter_validators.py +0 -0
  218. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/types/validators/_workflow_task_arguments_validators.py +0 -0
  219. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/urls.py +0 -0
  220. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/fractal_server/utils.py +0 -0
  221. {fractal_server-2.14.4a0 → fractal_server-2.14.5}/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.14.4a0
3
+ Version: 2.14.5
4
4
  Summary: Backend component of the Fractal analytics platform
5
5
  License: BSD-3-Clause
6
6
  Author: Tommaso Comparin
@@ -12,7 +12,6 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Requires-Dist: alembic (>=1.13.1,<2.0.0)
15
- Requires-Dist: cloudpickle (>=3.1.0,<3.2.0)
16
15
  Requires-Dist: cryptography (>=44.0.1,<44.1.0)
17
16
  Requires-Dist: fabric (>=3.2.2,<3.3.0)
18
17
  Requires-Dist: fastapi (>=0.115.0,<0.116.0)
@@ -65,8 +64,3 @@ Fractal was conceived in the Liberali Lab at the Friedrich Miescher Institute fo
65
64
 
66
65
  Unless otherwise specified, Fractal components are released under the BSD 3-Clause License, and copyright is with the BioVisionCenter at the University of Zurich.
67
66
 
68
- The SLURM compatibility layer is based on
69
- [`clusterfutures`](https://github.com/sampsyo/clusterfutures), by
70
- [@sampsyo](https://github.com/sampsyo) and collaborators, and it is released
71
- under the terms of the MIT license.
72
-
@@ -29,8 +29,3 @@ See https://fractal-analytics-platform.github.io/fractal-server.
29
29
  Fractal was conceived in the Liberali Lab at the Friedrich Miescher Institute for Biomedical Research and in the Pelkmans Lab at the University of Zurich by [@jluethi](https://github.com/jluethi) and [@gusqgm](https://github.com/gusqgm). The Fractal project is now developed at the [BioVisionCenter](https://www.biovisioncenter.uzh.ch/en.html) at the University of Zurich and the project lead is with [@jluethi](https://github.com/jluethi). The core development is done under contract by [eXact lab S.r.l.](https://www.exact-lab.it).
30
30
 
31
31
  Unless otherwise specified, Fractal components are released under the BSD 3-Clause License, and copyright is with the BioVisionCenter at the University of Zurich.
32
-
33
- The SLURM compatibility layer is based on
34
- [`clusterfutures`](https://github.com/sampsyo/clusterfutures), by
35
- [@sampsyo](https://github.com/sampsyo) and collaborators, and it is released
36
- under the terms of the MIT license.
@@ -0,0 +1 @@
1
+ __VERSION__ = "2.14.5"
@@ -46,12 +46,7 @@ class JobExecutionError(RuntimeError):
46
46
  Forwards errors in the execution of a task that are due to external factors
47
47
 
48
48
  This error wraps and forwards errors occurred during the execution of
49
- tasks, but related to external factors like:
50
-
51
- 1. A negative exit code (e.g. because the task received a TERM or KILL
52
- signal);
53
- 2. An error on the executor side (e.g. the SLURM executor could not
54
- find the pickled file with task output).
49
+ tasks, but related to external factors like an error on the executor side.
55
50
 
56
51
  This error also adds information that is useful to track down and debug the
57
52
  failing task within a workflow.
@@ -26,7 +26,10 @@ class BaseRunner(object):
26
26
 
27
27
  def submit(
28
28
  self,
29
- func: callable,
29
+ base_command: str,
30
+ workflow_task_order: int,
31
+ workflow_task_id: int,
32
+ task_name: str,
30
33
  parameters: dict[str, Any],
31
34
  history_unit_id: int,
32
35
  task_type: TaskTypeType,
@@ -38,7 +41,10 @@ class BaseRunner(object):
38
41
  Run a single fractal task.
39
42
 
40
43
  Args:
41
- func: Function to be executed.
44
+ base_command:
45
+ workflow_task_order:
46
+ workflow_task_id:
47
+ task_name:
42
48
  parameters: Dictionary of parameters.
43
49
  history_unit_id:
44
50
  Database ID of the corresponding `HistoryUnit` entry.
@@ -51,7 +57,10 @@ class BaseRunner(object):
51
57
 
52
58
  def multisubmit(
53
59
  self,
54
- func: callable,
60
+ base_command: str,
61
+ workflow_task_order: int,
62
+ workflow_task_id: int,
63
+ task_name: str,
55
64
  list_parameters: list[dict[str, Any]],
56
65
  history_unit_ids: list[int],
57
66
  list_task_files: list[TaskFiles],
@@ -63,7 +72,10 @@ class BaseRunner(object):
63
72
  Run a parallel fractal task.
64
73
 
65
74
  Args:
66
- func: Function to be executed.
75
+ base_command:
76
+ workflow_task_order:
77
+ workflow_task_id:
78
+ task_name:
67
79
  parameters:
68
80
  Dictionary of parameters. Must include `zarr_urls` key.
69
81
  history_unit_ids:
@@ -0,0 +1,52 @@
1
+ import os
2
+ import shlex
3
+ import shutil
4
+ import subprocess # nosec
5
+
6
+ from fractal_server.app.runner.exceptions import TaskExecutionError
7
+ from fractal_server.string_tools import validate_cmd
8
+
9
+
10
+ def call_command_wrapper(*, cmd: str, log_path: str) -> None:
11
+ """
12
+ Call a command and write its stdout and stderr to files
13
+
14
+ Args:
15
+ cmd:
16
+ log_path:
17
+ """
18
+ try:
19
+ validate_cmd(cmd)
20
+ except ValueError as e:
21
+ raise TaskExecutionError(f"Invalid command. Original error: {str(e)}")
22
+
23
+ split_cmd = shlex.split(cmd)
24
+
25
+ # Verify that task command is executable
26
+ if shutil.which(split_cmd[0]) is None:
27
+ msg = (
28
+ f'Command "{split_cmd[0]}" is not valid. '
29
+ "Hint: make sure that it is executable."
30
+ )
31
+ raise TaskExecutionError(msg)
32
+
33
+ with open(log_path, "w") as fp_log:
34
+ try:
35
+ result = subprocess.run( # nosec
36
+ split_cmd,
37
+ stderr=fp_log,
38
+ stdout=fp_log,
39
+ )
40
+ except Exception as e:
41
+ # This is likely unreachable
42
+ raise e
43
+
44
+ if result.returncode != 0:
45
+ stderr = ""
46
+ if os.path.isfile(log_path):
47
+ with open(log_path, "r") as fp_stderr:
48
+ stderr = fp_stderr.read()
49
+ raise TaskExecutionError(
50
+ f"Task failed with returncode={result.returncode}.\n"
51
+ f"STDERR: {stderr}"
52
+ )
@@ -1,9 +1,11 @@
1
+ import json
1
2
  from concurrent.futures import Future
2
3
  from concurrent.futures import ThreadPoolExecutor
3
4
  from pathlib import Path
4
5
  from typing import Any
5
6
  from typing import Literal
6
7
 
8
+ from ..call_command_wrapper import call_command_wrapper
7
9
  from .get_local_config import LocalBackendConfig
8
10
  from fractal_server.app.db import get_sync_db
9
11
  from fractal_server.app.runner.exceptions import TaskExecutionError
@@ -16,10 +18,40 @@ from fractal_server.app.runner.v2.db_tools import update_status_of_history_unit
16
18
  from fractal_server.app.schemas.v2 import HistoryUnitStatus
17
19
  from fractal_server.logger import set_logger
18
20
 
19
-
20
21
  logger = set_logger(__name__)
21
22
 
22
23
 
24
+ def run_single_task(
25
+ base_command: str,
26
+ parameters: dict[str, Any],
27
+ task_files: TaskFiles,
28
+ ):
29
+
30
+ # Write args.json file
31
+ with open(task_files.args_file_local, "w") as f:
32
+ json.dump(parameters, f)
33
+
34
+ # Run command
35
+ full_command = (
36
+ f"{base_command} "
37
+ f"--args-json {task_files.args_file_local} "
38
+ f"--out-json {task_files.metadiff_file_local}"
39
+ )
40
+
41
+ call_command_wrapper(
42
+ cmd=full_command,
43
+ log_path=task_files.log_file_local,
44
+ )
45
+
46
+ try:
47
+ with open(task_files.metadiff_file_local, "r") as f:
48
+ out_meta = json.load(f)
49
+ return out_meta
50
+ except FileNotFoundError:
51
+ # Command completed, but it produced no metadiff file
52
+ return None
53
+
54
+
23
55
  class LocalRunner(BaseRunner):
24
56
  executor: ThreadPoolExecutor
25
57
  root_dir_local: Path
@@ -47,17 +79,20 @@ class LocalRunner(BaseRunner):
47
79
 
48
80
  def submit(
49
81
  self,
50
- func: callable,
82
+ base_command: str,
83
+ workflow_task_order: int,
84
+ workflow_task_id: int,
85
+ task_name: str,
51
86
  parameters: dict[str, Any],
52
87
  history_unit_id: int,
53
88
  task_files: TaskFiles,
89
+ config: LocalBackendConfig,
54
90
  task_type: Literal[
55
91
  "non_parallel",
56
92
  "converter_non_parallel",
57
93
  "compound",
58
94
  "converter_compound",
59
95
  ],
60
- config: LocalBackendConfig,
61
96
  user_id: int,
62
97
  ) -> tuple[Any, Exception]:
63
98
  logger.debug("[submit] START")
@@ -69,9 +104,10 @@ class LocalRunner(BaseRunner):
69
104
 
70
105
  # SUBMISSION PHASE
71
106
  future = self.executor.submit(
72
- func,
107
+ run_single_task,
108
+ base_command=base_command,
73
109
  parameters=parameters,
74
- remote_files=task_files.remote_files_dict,
110
+ task_files=task_files,
75
111
  )
76
112
  except Exception as e:
77
113
  logger.error(
@@ -111,7 +147,10 @@ class LocalRunner(BaseRunner):
111
147
 
112
148
  def multisubmit(
113
149
  self,
114
- func: callable,
150
+ base_command: str,
151
+ workflow_task_order: int,
152
+ workflow_task_id: int,
153
+ task_name: str,
115
154
  list_parameters: list[dict],
116
155
  history_unit_ids: list[int],
117
156
  list_task_files: list[TaskFiles],
@@ -139,8 +178,9 @@ class LocalRunner(BaseRunner):
139
178
  )
140
179
 
141
180
  workdir_local = list_task_files[0].wftask_subfolder_local
142
- if task_type == "parallel":
143
- workdir_local.mkdir()
181
+ # Note: the `mkdir` is not needed for compound tasks, but it is
182
+ # needed for parallel tasks
183
+ workdir_local.mkdir(exist_ok=True)
144
184
 
145
185
  # Set `n_elements` and `parallel_tasks_per_job`
146
186
  n_elements = len(list_parameters)
@@ -178,11 +218,10 @@ class LocalRunner(BaseRunner):
178
218
  positional_index = ind_chunk + ind_within_chunk
179
219
  try:
180
220
  future = self.executor.submit(
181
- func,
182
- parameters=kwargs,
183
- remote_files=list_task_files[
184
- positional_index
185
- ].remote_files_dict,
221
+ run_single_task,
222
+ base_command=base_command,
223
+ parameters=list_parameters[positional_index],
224
+ task_files=list_task_files[positional_index],
186
225
  )
187
226
  active_futures[positional_index] = future
188
227
  except Exception as e:
@@ -7,7 +7,8 @@ from typing import Any
7
7
  from typing import Literal
8
8
  from typing import Optional
9
9
 
10
- import cloudpickle
10
+ from pydantic import BaseModel
11
+ from pydantic import ConfigDict
11
12
 
12
13
  from ..slurm_common._slurm_config import SlurmConfig
13
14
  from ..slurm_common.slurm_job_task_models import SlurmJob
@@ -36,6 +37,17 @@ SHUTDOWN_EXCEPTION = JobExecutionError(SHUTDOWN_ERROR_MESSAGE)
36
37
  logger = set_logger(__name__)
37
38
 
38
39
 
40
+ class RemoteInputData(BaseModel):
41
+ model_config = ConfigDict(extra="forbid")
42
+
43
+ python_version: tuple[int, int, int]
44
+ fractal_server_version: str
45
+ full_command: str
46
+
47
+ metadiff_file_remote: str
48
+ log_file_remote: str
49
+
50
+
39
51
  def create_accounting_record_slurm(
40
52
  *,
41
53
  user_id: int,
@@ -121,7 +133,6 @@ class BaseSlurmRunner(BaseRunner):
121
133
  raise NotImplementedError("Implement in child class.")
122
134
 
123
135
  def _get_finished_jobs(self, job_ids: list[str]) -> set[str]:
124
-
125
136
  # If there is no Slurm job to check, return right away
126
137
  if not job_ids:
127
138
  return set()
@@ -168,58 +179,74 @@ class BaseSlurmRunner(BaseRunner):
168
179
 
169
180
  def _submit_single_sbatch(
170
181
  self,
171
- func,
182
+ *,
183
+ base_command: str,
172
184
  slurm_job: SlurmJob,
173
185
  slurm_config: SlurmConfig,
174
186
  ) -> str:
175
187
  logger.debug("[_submit_single_sbatch] START")
176
- # Prepare input pickle(s)
177
- versions = dict(
178
- python=sys.version_info[:3],
179
- cloudpickle=cloudpickle.__version__,
180
- fractal_server=__VERSION__,
181
- )
188
+
182
189
  for task in slurm_job.tasks:
183
- # Write input pickle
184
- _args = []
185
- _kwargs = dict(
186
- parameters=task.parameters,
187
- remote_files=task.task_files.remote_files_dict,
190
+ # Write input file
191
+ if self.slurm_runner_type == "ssh":
192
+ args_file_remote = task.task_files.args_file_remote
193
+ else:
194
+ args_file_remote = task.task_files.args_file_local
195
+ metadiff_file_remote = task.task_files.metadiff_file_remote
196
+ full_command = (
197
+ f"{base_command} "
198
+ f"--args-json {args_file_remote} "
199
+ f"--out-json {metadiff_file_remote}"
188
200
  )
189
- funcser = cloudpickle.dumps((versions, func, _args, _kwargs))
190
- with open(task.input_pickle_file_local, "wb") as f:
191
- f.write(funcser)
201
+
202
+ input_data = RemoteInputData(
203
+ full_command=full_command,
204
+ python_version=sys.version_info[:3],
205
+ fractal_server_version=__VERSION__,
206
+ metadiff_file_remote=task.task_files.metadiff_file_remote,
207
+ log_file_remote=task.task_files.log_file_remote,
208
+ )
209
+
210
+ with open(task.input_file_local, "w") as f:
211
+ json.dump(input_data.model_dump(), f, indent=2)
212
+
213
+ with open(task.task_files.args_file_local, "w") as f:
214
+ json.dump(task.parameters, f, indent=2)
215
+
192
216
  logger.debug(
193
- "[_submit_single_sbatch] Written "
194
- f"{task.input_pickle_file_local=}"
217
+ "[_submit_single_sbatch] Written " f"{task.input_file_local=}"
195
218
  )
196
219
 
197
220
  if self.slurm_runner_type == "ssh":
198
- # Send input pickle (only relevant for SSH)
221
+ # Send input file (only relevant for SSH)
199
222
  self.fractal_ssh.send_file(
200
- local=task.input_pickle_file_local,
201
- remote=task.input_pickle_file_remote,
223
+ local=task.input_file_local,
224
+ remote=task.input_file_remote,
225
+ )
226
+ self.fractal_ssh.send_file(
227
+ local=task.task_files.args_file_local,
228
+ remote=task.task_files.args_file_remote,
202
229
  )
203
230
  logger.debug(
204
231
  "[_submit_single_sbatch] Transferred "
205
- f"{task.input_pickle_file_local=}"
232
+ f"{task.input_file_local=}"
206
233
  )
207
234
 
208
235
  # Prepare commands to be included in SLURM submission script
209
236
  cmdlines = []
210
237
  for task in slurm_job.tasks:
211
238
  if self.slurm_runner_type == "ssh":
212
- input_pickle_file = task.input_pickle_file_remote
239
+ input_file = task.input_file_remote
213
240
  else:
214
- input_pickle_file = task.input_pickle_file_local
215
- output_pickle_file = task.output_pickle_file_remote
241
+ input_file = task.input_file_local
242
+ output_file = task.output_file_remote
216
243
  cmdlines.append(
217
244
  (
218
245
  f"{self.python_worker_interpreter}"
219
246
  " -m fractal_server.app.runner."
220
247
  "executors.slurm_common.remote "
221
- f"--input-file {input_pickle_file} "
222
- f"--output-file {output_pickle_file}"
248
+ f"--input-file {input_file} "
249
+ f"--output-file {output_file}"
223
250
  )
224
251
  )
225
252
 
@@ -363,12 +390,12 @@ class BaseSlurmRunner(BaseRunner):
363
390
  was_job_scancelled: bool = False,
364
391
  ) -> tuple[Any, Exception]:
365
392
  try:
366
- with open(task.output_pickle_file_local, "rb") as f:
367
- outdata = f.read()
368
- success, output = cloudpickle.loads(outdata)
393
+ with open(task.output_file_local, "r") as f:
394
+ output = json.load(f)
395
+ success = output[0]
369
396
  if success:
370
397
  # Task succeeded
371
- result = output
398
+ result = output[1]
372
399
  return (result, None)
373
400
  else:
374
401
  # Task failed in a controlled way, and produced an `output`
@@ -376,21 +403,18 @@ class BaseSlurmRunner(BaseRunner):
376
403
  # `exc_type_name` and `traceback_string` and with optional
377
404
  # keys `workflow_task_order`, `workflow_task_id` and
378
405
  # `task_name`.
379
- exc_type_name = output.get("exc_type_name")
406
+ exc_proxy = output[1]
407
+ exc_type_name = exc_proxy.get("exc_type_name")
380
408
  logger.debug(
381
- f"Output pickle contains a '{exc_type_name}' exception."
409
+ f"Output file contains a '{exc_type_name}' exception."
410
+ )
411
+ traceback_string = output[1].get("traceback_string")
412
+ exception = TaskExecutionError(
413
+ traceback_string,
414
+ workflow_task_id=task.workflow_task_id,
415
+ workflow_task_order=task.workflow_task_order,
416
+ task_name=task.task_name,
382
417
  )
383
- traceback_string = output.get("traceback_string")
384
- kwargs = {
385
- key: output[key]
386
- for key in [
387
- "workflow_task_order",
388
- "workflow_task_id",
389
- "task_name",
390
- ]
391
- if key in output.keys()
392
- }
393
- exception = TaskExecutionError(traceback_string, **kwargs)
394
418
  return (None, exception)
395
419
 
396
420
  except Exception as e:
@@ -405,8 +429,8 @@ class BaseSlurmRunner(BaseRunner):
405
429
  exception = SHUTDOWN_EXCEPTION
406
430
  return (None, exception)
407
431
  finally:
408
- Path(task.input_pickle_file_local).unlink(missing_ok=True)
409
- Path(task.output_pickle_file_local).unlink(missing_ok=True)
432
+ Path(task.input_file_local).unlink(missing_ok=True)
433
+ Path(task.output_file_local).unlink(missing_ok=True)
410
434
 
411
435
  def is_shutdown(self) -> bool:
412
436
  return self.shutdown_file.exists()
@@ -451,7 +475,10 @@ class BaseSlurmRunner(BaseRunner):
451
475
 
452
476
  def submit(
453
477
  self,
454
- func: callable,
478
+ base_command: str,
479
+ workflow_task_order: int,
480
+ workflow_task_id: int,
481
+ task_name: str,
455
482
  parameters: dict[str, Any],
456
483
  history_unit_id: int,
457
484
  task_files: TaskFiles,
@@ -507,13 +534,16 @@ class BaseSlurmRunner(BaseRunner):
507
534
  workdir_remote=workdir_remote,
508
535
  workdir_local=workdir_local,
509
536
  task_files=task_files,
537
+ workflow_task_order=workflow_task_order,
538
+ workflow_task_id=workflow_task_id,
539
+ task_name=task_name,
510
540
  )
511
541
  ],
512
542
  )
513
543
 
514
544
  config.parallel_tasks_per_job = 1
515
545
  self._submit_single_sbatch(
516
- func,
546
+ base_command=base_command,
517
547
  slurm_job=slurm_job,
518
548
  slurm_config=config,
519
549
  )
@@ -586,7 +616,10 @@ class BaseSlurmRunner(BaseRunner):
586
616
 
587
617
  def multisubmit(
588
618
  self,
589
- func: callable,
619
+ base_command: str,
620
+ workflow_task_order: int,
621
+ workflow_task_id: int,
622
+ task_name: str,
590
623
  list_parameters: list[dict],
591
624
  history_unit_ids: list[int],
592
625
  list_task_files: list[TaskFiles],
@@ -602,7 +635,6 @@ class BaseSlurmRunner(BaseRunner):
602
635
 
603
636
  logger.debug(f"[multisubmit] START, {len(list_parameters)=}")
604
637
  try:
605
-
606
638
  if self.is_shutdown():
607
639
  if task_type == "parallel":
608
640
  with next(get_sync_db()) as db:
@@ -672,6 +704,9 @@ class BaseSlurmRunner(BaseRunner):
672
704
  parameters=parameters,
673
705
  zarr_url=parameters["zarr_url"],
674
706
  task_files=list_task_files[index],
707
+ workflow_task_order=workflow_task_order,
708
+ workflow_task_id=workflow_task_id,
709
+ task_name=task_name,
675
710
  ),
676
711
  )
677
712
  jobs_to_submit.append(
@@ -687,7 +722,7 @@ class BaseSlurmRunner(BaseRunner):
687
722
  logger.debug("[multisubmit] Transfer files and submit jobs.")
688
723
  for slurm_job in jobs_to_submit:
689
724
  self._submit_single_sbatch(
690
- func,
725
+ base_command=base_command,
691
726
  slurm_job=slurm_job,
692
727
  slurm_config=config,
693
728
  )
@@ -0,0 +1,125 @@
1
+ import argparse
2
+ import json
3
+ import logging
4
+ import os
5
+ import sys
6
+
7
+ from ..call_command_wrapper import call_command_wrapper
8
+ from fractal_server import __VERSION__
9
+
10
+
11
+ class FractalVersionMismatch(RuntimeError):
12
+ """
13
+ Custom exception for version mismatch
14
+ """
15
+
16
+ pass
17
+
18
+
19
+ def worker(
20
+ *,
21
+ in_fname: str,
22
+ out_fname: str,
23
+ ) -> None:
24
+ """
25
+ Execute a job, possibly on a remote node.
26
+
27
+ Arguments:
28
+ in_fname: Absolute path to the input file (must be readable).
29
+ out_fname: Absolute path of the output file (must be writeable).
30
+ """
31
+
32
+ # Create output folder, if missing
33
+ out_dir = os.path.dirname(out_fname)
34
+ if not os.path.exists(out_dir):
35
+ logging.debug(f"_slurm.remote.worker: create {out_dir=}")
36
+ os.mkdir(out_dir)
37
+
38
+ # Execute the job and capture exceptions
39
+ try:
40
+ with open(in_fname, "r") as f:
41
+ input_data = json.load(f)
42
+
43
+ server_python_version = input_data["python_version"]
44
+ server_fractal_server_version = input_data["fractal_server_version"]
45
+
46
+ # Fractal-server version must be identical
47
+ worker_fractal_server_version = __VERSION__
48
+ if worker_fractal_server_version != server_fractal_server_version:
49
+ raise FractalVersionMismatch(
50
+ f"{server_fractal_server_version=} but "
51
+ f"{worker_fractal_server_version=}"
52
+ )
53
+
54
+ # Python version mismatch only raises a warning
55
+ worker_python_version = tuple(sys.version_info[:3])
56
+ if worker_python_version != server_python_version:
57
+ if worker_python_version[:2] != server_python_version[:2]:
58
+ logging.warning(
59
+ f"{server_python_version=} but {worker_python_version=}."
60
+ )
61
+
62
+ # Extract some useful paths
63
+ metadiff_file_remote = input_data["metadiff_file_remote"]
64
+ log_path = input_data["log_file_remote"]
65
+
66
+ # Execute command
67
+ full_command = input_data["full_command"]
68
+ call_command_wrapper(cmd=full_command, log_path=log_path)
69
+
70
+ try:
71
+ with open(metadiff_file_remote, "r") as f:
72
+ out_meta = json.load(f)
73
+ result = (True, out_meta)
74
+ except FileNotFoundError:
75
+ # Command completed, but it produced no metadiff file
76
+ result = (True, None)
77
+
78
+ except Exception as e:
79
+ # Exception objects are not serialisable. Here we save the relevant
80
+ # exception contents in a serializable dictionary. Note that whenever
81
+ # the task failed "properly", the exception is a `TaskExecutionError`
82
+ # and it has additional attributes.
83
+ import traceback
84
+
85
+ exc_type, exc_value, traceback_obj = sys.exc_info()
86
+ traceback_obj = traceback_obj.tb_next
87
+ traceback_list = traceback.format_exception(
88
+ exc_type,
89
+ exc_value,
90
+ traceback_obj,
91
+ )
92
+ traceback_string = "".join(traceback_list)
93
+ exc_proxy = dict(
94
+ exc_type_name=type(e).__name__,
95
+ traceback_string=traceback_string,
96
+ )
97
+ result = (False, exc_proxy)
98
+
99
+ # Write output file
100
+ with open(out_fname, "w") as f:
101
+ json.dump(result, f, indent=2)
102
+
103
+
104
+ if __name__ == "__main__":
105
+ parser = argparse.ArgumentParser()
106
+ parser.add_argument(
107
+ "--input-file",
108
+ type=str,
109
+ help="Path of input JSON file",
110
+ required=True,
111
+ )
112
+ parser.add_argument(
113
+ "--output-file",
114
+ type=str,
115
+ help="Path of output JSON file",
116
+ required=True,
117
+ )
118
+ parsed_args = parser.parse_args()
119
+ logging.debug(f"{parsed_args=}")
120
+
121
+ kwargs = dict(
122
+ in_fname=parsed_args.input_file,
123
+ out_fname=parsed_args.output_file,
124
+ )
125
+ worker(**kwargs)