fractal-server 2.14.0a22__tar.gz → 2.14.0a23__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 (215) hide show
  1. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/PKG-INFO +1 -1
  2. fractal_server-2.14.0a23/fractal_server/__init__.py +1 -0
  3. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/compress_folder.py +27 -17
  4. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/base_slurm_runner.py +46 -16
  5. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/slurm_job_task_models.py +48 -16
  6. fractal_server-2.14.0a23/fractal_server/app/runner/executors/slurm_ssh/runner.py +208 -0
  7. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_sudo/runner.py +29 -9
  8. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/extract_archive.py +1 -3
  9. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/task_files.py +18 -6
  10. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/ssh/_fabric.py +4 -2
  11. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/pyproject.toml +2 -2
  12. fractal_server-2.14.0a22/fractal_server/__init__.py +0 -1
  13. fractal_server-2.14.0a22/fractal_server/app/runner/executors/slurm_ssh/runner.py +0 -172
  14. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/LICENSE +0 -0
  15. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/README.md +0 -0
  16. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/__main__.py +0 -0
  17. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/alembic.ini +0 -0
  18. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/__init__.py +0 -0
  19. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/db/__init__.py +0 -0
  20. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/history/__init__.py +0 -0
  21. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/__init__.py +0 -0
  22. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/linkusergroup.py +0 -0
  23. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/linkuserproject.py +0 -0
  24. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/security.py +0 -0
  25. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/user_settings.py +0 -0
  26. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/__init__.py +0 -0
  27. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/accounting.py +0 -0
  28. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/dataset.py +0 -0
  29. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/history.py +0 -0
  30. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/job.py +0 -0
  31. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/project.py +0 -0
  32. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/task.py +0 -0
  33. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/task_group.py +0 -0
  34. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/workflow.py +0 -0
  35. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/workflowtask.py +0 -0
  36. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/__init__.py +0 -0
  37. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/__init__.py +0 -0
  38. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/__init__.py +0 -0
  39. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/accounting.py +0 -0
  40. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/impersonate.py +0 -0
  41. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/job.py +0 -0
  42. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/project.py +0 -0
  43. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/task.py +0 -0
  44. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/task_group.py +0 -0
  45. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/task_group_lifecycle.py +0 -0
  46. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/__init__.py +0 -0
  47. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/__init__.py +0 -0
  48. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/_aux_functions.py +0 -0
  49. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/_aux_functions_history.py +0 -0
  50. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +0 -0
  51. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/_aux_functions_tasks.py +0 -0
  52. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/dataset.py +0 -0
  53. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/history.py +0 -0
  54. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/images.py +0 -0
  55. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/job.py +0 -0
  56. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/project.py +0 -0
  57. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/status_legacy.py +0 -0
  58. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/submit.py +0 -0
  59. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task.py +0 -0
  60. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task_collection.py +0 -0
  61. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task_collection_custom.py +0 -0
  62. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task_group.py +0 -0
  63. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task_group_lifecycle.py +0 -0
  64. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/verify_image_types.py +0 -0
  65. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/workflow.py +0 -0
  66. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/workflow_import.py +0 -0
  67. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/workflowtask.py +0 -0
  68. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/__init__.py +0 -0
  69. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/_aux_auth.py +0 -0
  70. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/current_user.py +0 -0
  71. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/group.py +0 -0
  72. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/login.py +0 -0
  73. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/oauth.py +0 -0
  74. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/register.py +0 -0
  75. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/router.py +0 -0
  76. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/users.py +0 -0
  77. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/aux/__init__.py +0 -0
  78. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/aux/_job.py +0 -0
  79. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/aux/_runner.py +0 -0
  80. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/aux/validate_user_settings.py +0 -0
  81. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/routes/pagination.py +0 -0
  82. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/__init__.py +0 -0
  83. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/components.py +0 -0
  84. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/exceptions.py +0 -0
  85. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/__init__.py +0 -0
  86. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/base_runner.py +0 -0
  87. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/local/__init__.py +0 -0
  88. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/local/get_local_config.py +0 -0
  89. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/local/runner.py +0 -0
  90. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/__init__.py +0 -0
  91. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/_batching.py +0 -0
  92. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/_job_states.py +0 -0
  93. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/_slurm_config.py +0 -0
  94. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/get_slurm_config.py +0 -0
  95. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/remote.py +0 -0
  96. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/utils_executors.py +0 -0
  97. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_ssh/__init__.py +0 -0
  98. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_sudo/__init__.py +0 -0
  99. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_sudo/_subprocess_run_as_user.py +0 -0
  100. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/filenames.py +0 -0
  101. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/run_subprocess.py +0 -0
  102. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/set_start_and_last_task_index.py +0 -0
  103. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/shutdown.py +0 -0
  104. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/__init__.py +0 -0
  105. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/_local.py +0 -0
  106. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/_slurm_ssh.py +0 -0
  107. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/_slurm_sudo.py +0 -0
  108. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/db_tools.py +0 -0
  109. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/deduplicate_list.py +0 -0
  110. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/merge_outputs.py +0 -0
  111. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/runner.py +0 -0
  112. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/runner_functions.py +0 -0
  113. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/runner_functions_low_level.py +0 -0
  114. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/submit_workflow.py +0 -0
  115. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/task_interface.py +0 -0
  116. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/runner/versions.py +0 -0
  117. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/__init__.py +0 -0
  118. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/_filter_validators.py +0 -0
  119. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/_validators.py +0 -0
  120. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/user.py +0 -0
  121. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/user_group.py +0 -0
  122. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/user_settings.py +0 -0
  123. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/__init__.py +0 -0
  124. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/accounting.py +0 -0
  125. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/dataset.py +0 -0
  126. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/dumps.py +0 -0
  127. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/history.py +0 -0
  128. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/job.py +0 -0
  129. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/manifest.py +0 -0
  130. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/project.py +0 -0
  131. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/status_legacy.py +0 -0
  132. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/task.py +0 -0
  133. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/task_collection.py +0 -0
  134. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/task_group.py +0 -0
  135. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/workflow.py +0 -0
  136. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/workflowtask.py +0 -0
  137. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/security/__init__.py +0 -0
  138. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/security/signup_email.py +0 -0
  139. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/app/user_settings.py +0 -0
  140. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/config.py +0 -0
  141. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/data_migrations/README.md +0 -0
  142. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/data_migrations/tools.py +0 -0
  143. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/gunicorn_fractal.py +0 -0
  144. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/images/__init__.py +0 -0
  145. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/images/models.py +0 -0
  146. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/images/tools.py +0 -0
  147. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/logger.py +0 -0
  148. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/main.py +0 -0
  149. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/env.py +0 -0
  150. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/naming_convention.py +0 -0
  151. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/034a469ec2eb_task_groups.py +0 -0
  152. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
  153. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +0 -0
  154. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +0 -0
  155. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/316140ff7ee1_remove_usersettings_cache_dir.py +0 -0
  156. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +0 -0
  157. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
  158. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
  159. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +0 -0
  160. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  161. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
  162. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  163. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
  164. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  165. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +0 -0
  166. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
  167. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +0 -0
  168. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
  169. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  170. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +0 -0
  171. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +0 -0
  172. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
  173. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  174. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +0 -0
  175. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +0 -0
  176. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +0 -0
  177. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
  178. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +0 -0
  179. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +0 -0
  180. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  181. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +0 -0
  182. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
  183. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  184. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +0 -0
  185. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/py.typed +0 -0
  186. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/ssh/__init__.py +0 -0
  187. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/string_tools.py +0 -0
  188. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/syringe.py +0 -0
  189. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/__init__.py +0 -0
  190. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/utils.py +0 -0
  191. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/__init__.py +0 -0
  192. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/__init__.py +0 -0
  193. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/_utils.py +0 -0
  194. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/collect.py +0 -0
  195. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/deactivate.py +0 -0
  196. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/reactivate.py +0 -0
  197. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/__init__.py +0 -0
  198. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/_utils.py +0 -0
  199. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/collect.py +0 -0
  200. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/deactivate.py +0 -0
  201. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/reactivate.py +0 -0
  202. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/1_create_venv.sh +0 -0
  203. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/2_pip_install.sh +0 -0
  204. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/3_pip_freeze.sh +0 -0
  205. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/4_pip_show.sh +0 -0
  206. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +0 -0
  207. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +0 -0
  208. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_background.py +0 -0
  209. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_database.py +0 -0
  210. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_package_names.py +0 -0
  211. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_python_interpreter.py +0 -0
  212. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_templates.py +0 -0
  213. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/urls.py +0 -0
  214. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/fractal_server/utils.py +0 -0
  215. {fractal_server-2.14.0a22 → fractal_server-2.14.0a23}/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.0a22
3
+ Version: 2.14.0a23
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.14.0a23"
@@ -35,24 +35,27 @@ def _create_tar_archive(
35
35
  tarfile_path: str,
36
36
  subfolder_path_tmp_copy: Path,
37
37
  logger_name: str,
38
- remote_to_local: bool,
38
+ filelist_path: str | None,
39
39
  ):
40
40
  logger = get_logger(logger_name)
41
41
  logger.debug(f"[_create_tar_archive] START ({tarfile_path})")
42
42
  t_start = time.perf_counter()
43
43
 
44
- if remote_to_local:
45
- exclude_options = "--exclude *sbatch --exclude *_in_*.pickle "
44
+ if filelist_path is None:
45
+ cmd_tar = (
46
+ f"tar -c -z -f {tarfile_path} "
47
+ f"--directory={subfolder_path_tmp_copy.as_posix()} "
48
+ "."
49
+ )
46
50
  else:
47
- exclude_options = ""
51
+ cmd_tar = (
52
+ f"tar -c -z -f {tarfile_path} "
53
+ f"--directory={subfolder_path_tmp_copy.as_posix()} "
54
+ f"--files-from={filelist_path} --ignore-failed-read"
55
+ )
56
+
57
+ logger.critical(f"cmd tar:\n{cmd_tar}")
48
58
 
49
- cmd_tar = (
50
- f"tar czf {tarfile_path} "
51
- f"{exclude_options} "
52
- f"--directory={subfolder_path_tmp_copy.as_posix()} "
53
- "."
54
- )
55
- logger.debug(f"cmd tar:\n{cmd_tar}")
56
59
  run_subprocess(cmd=cmd_tar, logger_name=logger_name, allow_char="*")
57
60
  elapsed = time.perf_counter() - t_start
58
61
  logger.debug(f"[_create_tar_archive] END {elapsed=} s ({tarfile_path})")
@@ -75,7 +78,8 @@ def _remove_temp_subfolder(subfolder_path_tmp_copy: Path, logger_name: str):
75
78
 
76
79
 
77
80
  def compress_folder(
78
- subfolder_path: Path, remote_to_local: bool = False
81
+ subfolder_path: Path,
82
+ filelist_path: str | None,
79
83
  ) -> str:
80
84
  """
81
85
  Compress e.g. `/path/archive` into `/path/archive.tar.gz`
@@ -114,7 +118,7 @@ def compress_folder(
114
118
  tarfile_path,
115
119
  subfolder_path_tmp_copy,
116
120
  logger_name=logger_name,
117
- remote_to_local=remote_to_local,
121
+ filelist_path=filelist_path,
118
122
  )
119
123
  return tarfile_path
120
124
 
@@ -133,15 +137,21 @@ def main(sys_argv: list[str]):
133
137
  help_msg = (
134
138
  "Expected use:\n"
135
139
  "python -m fractal_server.app.runner.compress_folder "
136
- "path/to/folder [--remote-to-local]\n"
140
+ "path/to/folder [--filelist /path/to/filelist]\n"
137
141
  )
138
142
  num_args = len(sys_argv[1:])
139
143
  if num_args == 0:
140
144
  sys.exit(f"Invalid argument.\n{help_msg}\nProvided: {sys_argv[1:]=}")
141
145
  elif num_args == 1:
142
- compress_folder(subfolder_path=Path(sys_argv[1]))
143
- elif num_args == 2 and sys_argv[2] == "--remote-to-local":
144
- compress_folder(subfolder_path=Path(sys_argv[1]), remote_to_local=True)
146
+ compress_folder(
147
+ subfolder_path=Path(sys_argv[1]),
148
+ filelist_path=None,
149
+ )
150
+ elif num_args == 3 and sys_argv[2] == "--filelist":
151
+ compress_folder(
152
+ subfolder_path=Path(sys_argv[1]),
153
+ filelist_path=sys_argv[3],
154
+ )
145
155
  else:
146
156
  sys.exit(f"Invalid argument.\n{help_msg}\nProvided: {sys_argv[1:]=}")
147
157
 
@@ -60,6 +60,7 @@ class BaseSlurmRunner(BaseRunner):
60
60
  root_dir_local: Path,
61
61
  root_dir_remote: Path,
62
62
  slurm_runner_type: Literal["ssh", "sudo"],
63
+ python_worker_interpreter: str,
63
64
  common_script_lines: Optional[list[str]] = None,
64
65
  user_cache_dir: Optional[str] = None,
65
66
  poll_interval: Optional[int] = None,
@@ -70,6 +71,7 @@ class BaseSlurmRunner(BaseRunner):
70
71
  self.common_script_lines = common_script_lines or []
71
72
  self._check_slurm_account()
72
73
  self.user_cache_dir = user_cache_dir
74
+ self.python_worker_interpreter = python_worker_interpreter
73
75
 
74
76
  settings = Inject(get_settings)
75
77
 
@@ -327,9 +329,9 @@ class BaseSlurmRunner(BaseRunner):
327
329
  )
328
330
  logger.info("[_submit_single_sbatch] END")
329
331
 
330
- def _copy_files_from_remote_to_local(
332
+ def _fetch_artifacts(
331
333
  self,
332
- slurm_job: SlurmJob,
334
+ finished_slurm_jobs: list[SlurmJob],
333
335
  ) -> None:
334
336
  raise NotImplementedError("Implement in child class.")
335
337
 
@@ -530,14 +532,14 @@ class BaseSlurmRunner(BaseRunner):
530
532
  # Look for finished jobs
531
533
  finished_job_ids = self._get_finished_jobs(job_ids=self.job_ids)
532
534
  logger.debug(f"[submit] {finished_job_ids=}")
533
-
535
+ finished_jobs = [
536
+ self.jobs[_slurm_job_id] for _slurm_job_id in finished_job_ids
537
+ ]
538
+ self._fetch_artifacts(finished_jobs)
534
539
  with next(get_sync_db()) as db:
535
540
  for slurm_job_id in finished_job_ids:
536
541
  logger.debug(f"[submit] Now process {slurm_job_id=}")
537
542
  slurm_job = self.jobs.pop(slurm_job_id)
538
- self._copy_files_from_remote_to_local(
539
- slurm_job
540
- ) # FIXME: add prefix # noqa
541
543
  was_job_scancelled = slurm_job_id in scancelled_job_ids
542
544
  result, exception = self._postprocess_single_task(
543
545
  task=slurm_job.tasks[0],
@@ -653,7 +655,9 @@ class BaseSlurmRunner(BaseRunner):
653
655
  if len(args_batches) != math.ceil(tot_tasks / tasks_per_job):
654
656
  raise RuntimeError("Something wrong here while batching tasks")
655
657
 
656
- logger.info(f"START submission phase, {list(self.jobs.keys())=}")
658
+ # Part 1/3: Iterate over chunks, prepare SlurmJob objects
659
+ logger.info("[multisubmit] Prepare `SlurmJob`s.")
660
+ jobs_to_submit = []
657
661
  for ind_batch, chunk in enumerate(args_batches):
658
662
  prefix = f"{MULTISUBMIT_PREFIX}-{ind_batch:06d}"
659
663
  tasks = []
@@ -673,17 +677,26 @@ class BaseSlurmRunner(BaseRunner):
673
677
  ),
674
678
  )
675
679
 
676
- slurm_job = SlurmJob(
677
- prefix=prefix,
678
- workdir_local=workdir_local,
679
- workdir_remote=workdir_remote,
680
- tasks=tasks,
680
+ jobs_to_submit.append(
681
+ SlurmJob(
682
+ prefix=prefix,
683
+ workdir_local=workdir_local,
684
+ workdir_remote=workdir_remote,
685
+ tasks=tasks,
686
+ )
681
687
  )
688
+
689
+ # FIXME: split parts 2 and 3
690
+ # Part 2/3. Transfer all relevant input files (for SSH)
691
+ # Part 3/3. Run all `sbatch`es and update `self.jobs`
692
+ logger.info("[multisubmit] Transfer files and submit jobs.")
693
+ for slurm_job in jobs_to_submit:
682
694
  self._submit_single_sbatch(
683
695
  func,
684
696
  slurm_job=slurm_job,
685
697
  slurm_config=config,
686
698
  )
699
+
687
700
  if task_type == "parallel":
688
701
  # FIXME: replace loop with a `bulk_update_history_unit` function
689
702
  for ind, task_files in enumerate(list_task_files):
@@ -711,20 +724,21 @@ class BaseSlurmRunner(BaseRunner):
711
724
 
712
725
  # Retrieval phase
713
726
  logger.info("[multisubmit] START retrieval phase")
727
+ scancelled_job_ids = []
714
728
  while len(self.jobs) > 0:
715
729
 
716
730
  # Look for finished jobs
717
731
  finished_job_ids = self._get_finished_jobs(job_ids=self.job_ids)
718
732
  logger.debug(f"[multisubmit] {finished_job_ids=}")
733
+ finished_jobs = [
734
+ self.jobs[_slurm_job_id] for _slurm_job_id in finished_job_ids
735
+ ]
736
+ self._fetch_artifacts(finished_jobs)
719
737
 
720
- scancelled_job_ids = []
721
738
  with next(get_sync_db()) as db:
722
739
  for slurm_job_id in finished_job_ids:
723
740
  logger.info(f"[multisubmit] Now process {slurm_job_id=}")
724
741
  slurm_job = self.jobs.pop(slurm_job_id)
725
- self._copy_files_from_remote_to_local(
726
- slurm_job
727
- ) # FIXME: add prefix # noqa
728
742
  for task in slurm_job.tasks:
729
743
  logger.info(f"[multisubmit] Now process {task.index=}")
730
744
  was_job_scancelled = slurm_job_id in scancelled_job_ids
@@ -810,3 +824,19 @@ class BaseSlurmRunner(BaseRunner):
810
824
  )
811
825
  logger.info("[scancel_jobs] END")
812
826
  return scancelled_job_ids
827
+
828
+ def validate_slurm_jobs_workdirs(
829
+ self,
830
+ slurm_jobs: list[SlurmJob],
831
+ ) -> None:
832
+ """
833
+ Check that a list of `SlurmJob`s have homogeneous working folders.
834
+ """
835
+ # Extract `workdir_remote` and `workdir_local`
836
+ set_workdir_local = set(_job.workdir_local for _job in slurm_jobs)
837
+ set_workdir_remote = set(_job.workdir_remote for _job in slurm_jobs)
838
+
839
+ if len(set_workdir_local) > 1:
840
+ raise ValueError(f"Non-unique values in {set_workdir_local=}.")
841
+ if len(set_workdir_remote) > 1:
842
+ raise ValueError(f"Non-unique values in {set_workdir_remote=}.")
@@ -20,31 +20,47 @@ class SlurmTask(BaseModel):
20
20
  index: int
21
21
 
22
22
  @property
23
- def input_pickle_file_local(self) -> str:
23
+ def input_pickle_file_local_path(self) -> Path:
24
24
  return (
25
25
  self.workdir_local / f"{self.prefix}-{self.component}-input.pickle"
26
- ).as_posix()
26
+ )
27
27
 
28
28
  @property
29
- def input_pickle_file_remote(self) -> str:
29
+ def input_pickle_file_remote_path(self) -> Path:
30
30
  return (
31
31
  self.workdir_remote
32
32
  / f"{self.prefix}-{self.component}-input.pickle"
33
- ).as_posix()
33
+ )
34
34
 
35
35
  @property
36
- def output_pickle_file_local(self) -> str:
36
+ def output_pickle_file_local_path(self) -> Path:
37
37
  return (
38
38
  self.workdir_local
39
39
  / f"{self.prefix}-{self.component}-output.pickle"
40
- ).as_posix()
40
+ )
41
41
 
42
42
  @property
43
- def output_pickle_file_remote(self) -> str:
43
+ def output_pickle_file_remote_path(self) -> Path:
44
44
  return (
45
45
  self.workdir_remote
46
46
  / f"{self.prefix}-{self.component}-output.pickle"
47
- ).as_posix()
47
+ )
48
+
49
+ @property
50
+ def input_pickle_file_local(self) -> str:
51
+ return self.input_pickle_file_local_path.as_posix()
52
+
53
+ @property
54
+ def input_pickle_file_remote(self) -> str:
55
+ return self.input_pickle_file_remote_path.as_posix()
56
+
57
+ @property
58
+ def output_pickle_file_local(self) -> str:
59
+ return self.output_pickle_file_local_path.as_posix()
60
+
61
+ @property
62
+ def output_pickle_file_remote(self) -> str:
63
+ return self.output_pickle_file_remote_path.as_posix()
48
64
 
49
65
 
50
66
  class SlurmJob(BaseModel):
@@ -74,29 +90,45 @@ class SlurmJob(BaseModel):
74
90
  return "%j"
75
91
 
76
92
  @property
77
- def slurm_stdout_remote(self) -> str:
93
+ def slurm_stdout_remote_path(self) -> Path:
78
94
  return (
79
95
  self.workdir_remote
80
96
  / f"{self.prefix}-slurm-{self.slurm_job_id_placeholder}.out"
81
- ).as_posix()
97
+ )
82
98
 
83
99
  @property
84
- def slurm_stderr_remote(self) -> str:
100
+ def slurm_stdout_remote(self) -> str:
101
+ return self.slurm_stdout_remote_path.as_posix()
102
+
103
+ @property
104
+ def slurm_stderr_remote_path(self) -> Path:
85
105
  return (
86
106
  self.workdir_remote
87
107
  / f"{self.prefix}-slurm-{self.slurm_job_id_placeholder}.err"
88
- ).as_posix()
108
+ )
89
109
 
90
110
  @property
91
- def slurm_stdout_local(self) -> str:
111
+ def slurm_stderr_remote(self) -> str:
112
+ return self.slurm_stderr_remote_path.as_posix()
113
+
114
+ @property
115
+ def slurm_stdout_local_path(self) -> str:
92
116
  return (
93
117
  self.workdir_local
94
118
  / f"{self.prefix}-slurm-{self.slurm_job_id_placeholder}.out"
95
- ).as_posix()
119
+ )
96
120
 
97
121
  @property
98
- def slurm_stderr_local(self) -> str:
122
+ def slurm_stdout_local(self) -> str:
123
+ return self.slurm_stdout_local_path.as_posix()
124
+
125
+ @property
126
+ def slurm_stderr_local_path(self) -> Path:
99
127
  return (
100
128
  self.workdir_local
101
129
  / f"{self.prefix}-slurm-{self.slurm_job_id_placeholder}.err"
102
- ).as_posix()
130
+ )
131
+
132
+ @property
133
+ def slurm_stderr_local(self) -> str:
134
+ return self.slurm_stderr_local_path.as_posix()
@@ -0,0 +1,208 @@
1
+ import time
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ from ..slurm_common.base_slurm_runner import BaseSlurmRunner
6
+ from ..slurm_common.slurm_job_task_models import SlurmJob
7
+ from fractal_server.app.runner.compress_folder import compress_folder
8
+ from fractal_server.app.runner.extract_archive import extract_archive
9
+ from fractal_server.config import get_settings
10
+ from fractal_server.logger import set_logger
11
+ from fractal_server.ssh._fabric import FractalSSH
12
+ from fractal_server.syringe import Inject
13
+
14
+
15
+ logger = set_logger(__name__)
16
+
17
+
18
+ class SlurmSSHRunner(BaseSlurmRunner):
19
+ fractal_ssh: FractalSSH
20
+
21
+ def __init__(
22
+ self,
23
+ *,
24
+ # Common
25
+ root_dir_local: Path,
26
+ root_dir_remote: Path,
27
+ common_script_lines: Optional[list[str]] = None,
28
+ user_cache_dir: Optional[str] = None,
29
+ poll_interval: Optional[int] = None,
30
+ # Specific
31
+ fractal_ssh: FractalSSH,
32
+ ) -> None:
33
+ """
34
+ Set parameters that are the same for different Fractal tasks and for
35
+ different SLURM jobs/tasks.
36
+ """
37
+ self.fractal_ssh = fractal_ssh
38
+ logger.warning(self.fractal_ssh)
39
+
40
+ settings = Inject(get_settings)
41
+
42
+ super().__init__(
43
+ slurm_runner_type="ssh",
44
+ root_dir_local=root_dir_local,
45
+ root_dir_remote=root_dir_remote,
46
+ common_script_lines=common_script_lines,
47
+ user_cache_dir=user_cache_dir,
48
+ poll_interval=poll_interval,
49
+ python_worker_interpreter=settings.FRACTAL_SLURM_WORKER_PYTHON,
50
+ )
51
+
52
+ def _mkdir_local_folder(self, folder: str) -> None:
53
+ Path(folder).mkdir(parents=True)
54
+
55
+ def _mkdir_remote_folder(self, folder: str):
56
+ self.fractal_ssh.mkdir(
57
+ folder=folder,
58
+ parents=True,
59
+ )
60
+
61
+ def _fetch_artifacts(
62
+ self,
63
+ finished_slurm_jobs: list[SlurmJob],
64
+ ) -> None:
65
+ """
66
+ Fetch artifacts for a list of SLURM jobs.
67
+ """
68
+
69
+ # Check length
70
+ if len(finished_slurm_jobs) == 0:
71
+ logger.debug(f"[_fetch_artifacts] EXIT ({finished_slurm_jobs=}).")
72
+ return None
73
+
74
+ t_0 = time.perf_counter()
75
+ logger.debug(
76
+ f"[_fetch_artifacts] START ({len(finished_slurm_jobs)=})."
77
+ )
78
+
79
+ # Extract `workdir_remote` and `workdir_local`
80
+ self.validate_slurm_jobs_workdirs(finished_slurm_jobs)
81
+ workdir_local = finished_slurm_jobs[0].workdir_local
82
+ workdir_remote = finished_slurm_jobs[0].workdir_remote
83
+
84
+ # Define local/remote tarfile paths
85
+ tarfile_path_local = (
86
+ workdir_local.parent / f"{workdir_local.name}.tar.gz"
87
+ ).as_posix()
88
+ tarfile_path_remote = (
89
+ workdir_remote.parent / f"{workdir_remote.name}.tar.gz"
90
+ ).as_posix()
91
+
92
+ # Create file list
93
+ # # FIXME can we make this more efficient with iterations?
94
+ filelist = []
95
+ for _slurm_job in finished_slurm_jobs:
96
+ _single_job_filelist = [
97
+ _slurm_job.slurm_stdout_remote_path.name,
98
+ _slurm_job.slurm_stderr_remote_path.name,
99
+ ]
100
+ for task in _slurm_job.tasks:
101
+ _single_job_filelist.extend(
102
+ [
103
+ task.output_pickle_file_remote_path.name,
104
+ task.task_files.log_file_remote_path.name,
105
+ task.task_files.args_file_remote_path.name,
106
+ task.task_files.metadiff_file_remote_path.name,
107
+ ]
108
+ )
109
+ filelist.extend(_single_job_filelist)
110
+ filelist_string = "\n".join(filelist)
111
+ elapsed = time.perf_counter() - t_0
112
+ logger.debug(
113
+ "[_fetch_artifacts] Created filelist "
114
+ f"({len(filelist)=}, from start: {elapsed:.3f} s)."
115
+ )
116
+
117
+ # Write filelist to file remotely
118
+ tmp_filelist_path = workdir_remote / f"filelist_{time.time()}.txt"
119
+ self.fractal_ssh.write_remote_file(
120
+ path=tmp_filelist_path.as_posix(),
121
+ content=f"{filelist_string}\n",
122
+ )
123
+ elapsed = time.perf_counter() - t_0
124
+ logger.debug(
125
+ f"[_fetch_artifacts] File list written to {tmp_filelist_path} "
126
+ f"(from start: {elapsed:.3f} s)."
127
+ )
128
+
129
+ # Create remote tarfile
130
+ t_0_tar = time.perf_counter()
131
+ tar_command = (
132
+ f"{self.python_worker_interpreter} "
133
+ "-m fractal_server.app.runner.compress_folder "
134
+ f"{workdir_remote.as_posix()} "
135
+ f"--filelist {tmp_filelist_path}"
136
+ )
137
+ self.fractal_ssh.run_command(cmd=tar_command)
138
+ t_1_tar = time.perf_counter()
139
+ logger.info(
140
+ f"Remote archive {tarfile_path_remote} created"
141
+ f" - elapsed: {t_1_tar - t_0_tar:.3f} s"
142
+ )
143
+
144
+ # Fetch tarfile
145
+ t_0_get = time.perf_counter()
146
+ self.fractal_ssh.fetch_file(
147
+ remote=tarfile_path_remote,
148
+ local=tarfile_path_local,
149
+ )
150
+ t_1_get = time.perf_counter()
151
+ logger.info(
152
+ f"Subfolder archive transferred back to {tarfile_path_local}"
153
+ f" - elapsed: {t_1_get - t_0_get:.3f} s"
154
+ )
155
+
156
+ # Extract tarfile locally
157
+ extract_archive(Path(tarfile_path_local))
158
+
159
+ # Remove local tarfile
160
+ Path(tarfile_path_local).unlink(missing_ok=True)
161
+
162
+ t_1 = time.perf_counter()
163
+ logger.info(f"[_get_subfolder_sftp] End - elapsed: {t_1 - t_0:.3f} s")
164
+
165
+ def _send_inputs(self, jobs: list[SlurmJob]) -> None:
166
+ """
167
+ Transfer the jobs subfolder to the remote host.
168
+ """
169
+ for job in jobs:
170
+
171
+ # Create local archive
172
+ tarfile_path_local = compress_folder(
173
+ job.workdir_local,
174
+ filelist_path=None,
175
+ )
176
+ tarfile_name = Path(tarfile_path_local).name
177
+ logger.info(f"Subfolder archive created at {tarfile_path_local}")
178
+
179
+ # Transfer archive
180
+ tarfile_path_remote = (
181
+ job.workdir_remote.parent / tarfile_name
182
+ ).as_posix()
183
+ t_0_put = time.perf_counter()
184
+ self.fractal_ssh.send_file(
185
+ local=tarfile_path_local,
186
+ remote=tarfile_path_remote,
187
+ )
188
+ t_1_put = time.perf_counter()
189
+ logger.info(
190
+ f"Subfolder archive transferred to {tarfile_path_remote}"
191
+ f" - elapsed: {t_1_put - t_0_put:.3f} s"
192
+ )
193
+
194
+ # Remove local archive
195
+ Path(tarfile_path_local).unlink()
196
+ logger.debug(f"Local archive {tarfile_path_local} removed")
197
+
198
+ # Uncompress remote archive
199
+ tar_command = (
200
+ f"{self.python_worker_interpreter} -m "
201
+ "fractal_server.app.runner.extract_archive "
202
+ f"{tarfile_path_remote}"
203
+ )
204
+ self.fractal_ssh.run_command(cmd=tar_command)
205
+
206
+ def _run_remote_cmd(self, cmd: str) -> str:
207
+ stdout = self.fractal_ssh.run_command(cmd=cmd)
208
+ return stdout
@@ -3,6 +3,7 @@ import os
3
3
  import shlex
4
4
  import subprocess # nosec
5
5
  import sys
6
+ from concurrent.futures import ThreadPoolExecutor
6
7
  from pathlib import Path
7
8
  from typing import Optional
8
9
 
@@ -15,7 +16,6 @@ from fractal_server.config import get_settings
15
16
  from fractal_server.logger import set_logger
16
17
  from fractal_server.syringe import Inject
17
18
 
18
-
19
19
  logger = set_logger(__name__)
20
20
 
21
21
 
@@ -67,10 +67,6 @@ class SudoSlurmRunner(BaseSlurmRunner):
67
67
  self.slurm_account = slurm_account
68
68
  settings = Inject(get_settings)
69
69
 
70
- self.python_worker_interpreter = (
71
- settings.FRACTAL_SLURM_WORKER_PYTHON or sys.executable
72
- )
73
-
74
70
  super().__init__(
75
71
  slurm_runner_type="sudo",
76
72
  root_dir_local=root_dir_local,
@@ -78,6 +74,9 @@ class SudoSlurmRunner(BaseSlurmRunner):
78
74
  common_script_lines=common_script_lines,
79
75
  user_cache_dir=user_cache_dir,
80
76
  poll_interval=poll_interval,
77
+ python_worker_interpreter=(
78
+ settings.FRACTAL_SLURM_WORKER_PYTHON or sys.executable
79
+ ),
81
80
  )
82
81
 
83
82
  def _mkdir_local_folder(self, folder: str) -> None:
@@ -88,12 +87,12 @@ class SudoSlurmRunner(BaseSlurmRunner):
88
87
  def _mkdir_remote_folder(self, folder: str) -> None:
89
88
  _mkdir_as_user(folder=folder, user=self.slurm_user)
90
89
 
91
- def _copy_files_from_remote_to_local(self, job: SlurmJob) -> None:
90
+ def _fetch_artifacts_single_job(self, job: SlurmJob) -> None:
92
91
  """
93
- Note: this would differ for SSH
92
+ Fetch artifacts for a single SLURM jobs.
94
93
  """
95
94
  logger.debug(
96
- f"[_copy_files_from_remote_to_local] {job.slurm_job_id=} START"
95
+ f"[_fetch_artifacts_single_job] {job.slurm_job_id=} START"
97
96
  )
98
97
  source_target_list = [
99
98
  (job.slurm_stdout_remote, job.slurm_stdout_local),
@@ -140,9 +139,30 @@ class SudoSlurmRunner(BaseSlurmRunner):
140
139
  f"SKIP copy {source} into {target}. "
141
140
  f"Original error: {str(e)}"
142
141
  )
142
+ logger.debug(f"[_fetch_artifacts_single_job] {job.slurm_job_id=} END")
143
+
144
+ def _fetch_artifacts(
145
+ self,
146
+ finished_slurm_jobs: list[SlurmJob],
147
+ ) -> None:
148
+ """
149
+ Fetch artifacts for a list of SLURM jobs.
150
+ """
151
+ MAX_NUM_THREADS = 4
152
+ THREAD_NAME_PREFIX = "fetch_artifacts"
143
153
  logger.debug(
144
- f"[_copy_files_from_remote_to_local] {job.slurm_job_id=} END"
154
+ "[_fetch_artifacts] START "
155
+ f"({MAX_NUM_THREADS=}, {len(finished_slurm_jobs)=})."
145
156
  )
157
+ with ThreadPoolExecutor(
158
+ max_workers=MAX_NUM_THREADS,
159
+ thread_name_prefix=THREAD_NAME_PREFIX,
160
+ ) as executor:
161
+ executor.map(
162
+ self._fetch_artifacts_single_job,
163
+ finished_slurm_jobs,
164
+ )
165
+ logger.debug("[_fetch_artifacts] END.")
146
166
 
147
167
  def _run_remote_cmd(self, cmd: str) -> str:
148
168
  res = _run_command_as_user(
@@ -57,9 +57,7 @@ def extract_archive(archive_path: Path):
57
57
 
58
58
  # Run tar command
59
59
  cmd_tar = (
60
- f"tar -xzvf {archive_path} "
61
- f"--directory={subfolder_path.as_posix()} "
62
- "."
60
+ f"tar -xzvf {archive_path} " f"--directory={subfolder_path.as_posix()}"
63
61
  )
64
62
  logger.debug(f"{cmd_tar=}")
65
63
  run_subprocess(cmd=cmd_tar, logger_name=logger_name)