fractal-server 2.14.0a21__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.0a21 → 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.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/history.py +14 -2
  4. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/compress_folder.py +58 -30
  5. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/base_slurm_runner.py +46 -16
  6. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/slurm_job_task_models.py +48 -16
  7. fractal_server-2.14.0a23/fractal_server/app/runner/executors/slurm_ssh/runner.py +208 -0
  8. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_sudo/runner.py +29 -9
  9. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/extract_archive.py +1 -3
  10. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/task_files.py +18 -6
  11. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/ssh/_fabric.py +4 -2
  12. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/pyproject.toml +2 -2
  13. fractal_server-2.14.0a21/fractal_server/__init__.py +0 -1
  14. fractal_server-2.14.0a21/fractal_server/app/runner/executors/slurm_ssh/runner.py +0 -172
  15. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/LICENSE +0 -0
  16. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/README.md +0 -0
  17. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/__main__.py +0 -0
  18. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/alembic.ini +0 -0
  19. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/__init__.py +0 -0
  20. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/db/__init__.py +0 -0
  21. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/history/__init__.py +0 -0
  22. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/__init__.py +0 -0
  23. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/linkusergroup.py +0 -0
  24. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/linkuserproject.py +0 -0
  25. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/security.py +0 -0
  26. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/user_settings.py +0 -0
  27. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/__init__.py +0 -0
  28. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/accounting.py +0 -0
  29. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/dataset.py +0 -0
  30. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/history.py +0 -0
  31. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/job.py +0 -0
  32. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/project.py +0 -0
  33. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/task.py +0 -0
  34. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/task_group.py +0 -0
  35. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/workflow.py +0 -0
  36. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/models/v2/workflowtask.py +0 -0
  37. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/__init__.py +0 -0
  38. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/__init__.py +0 -0
  39. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/__init__.py +0 -0
  40. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/accounting.py +0 -0
  41. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/impersonate.py +0 -0
  42. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/job.py +0 -0
  43. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/project.py +0 -0
  44. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/task.py +0 -0
  45. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/task_group.py +0 -0
  46. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/admin/v2/task_group_lifecycle.py +0 -0
  47. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/__init__.py +0 -0
  48. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/__init__.py +0 -0
  49. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/_aux_functions.py +0 -0
  50. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/_aux_functions_history.py +0 -0
  51. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +0 -0
  52. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/_aux_functions_tasks.py +0 -0
  53. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/dataset.py +0 -0
  54. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/images.py +0 -0
  55. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/job.py +0 -0
  56. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/project.py +0 -0
  57. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/status_legacy.py +0 -0
  58. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/submit.py +0 -0
  59. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task.py +0 -0
  60. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task_collection.py +0 -0
  61. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task_collection_custom.py +0 -0
  62. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task_group.py +0 -0
  63. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/task_group_lifecycle.py +0 -0
  64. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/verify_image_types.py +0 -0
  65. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/workflow.py +0 -0
  66. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/workflow_import.py +0 -0
  67. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/api/v2/workflowtask.py +0 -0
  68. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/__init__.py +0 -0
  69. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/_aux_auth.py +0 -0
  70. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/current_user.py +0 -0
  71. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/group.py +0 -0
  72. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/login.py +0 -0
  73. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/oauth.py +0 -0
  74. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/register.py +0 -0
  75. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/router.py +0 -0
  76. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/auth/users.py +0 -0
  77. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/aux/__init__.py +0 -0
  78. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/aux/_job.py +0 -0
  79. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/aux/_runner.py +0 -0
  80. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/aux/validate_user_settings.py +0 -0
  81. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/routes/pagination.py +0 -0
  82. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/__init__.py +0 -0
  83. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/components.py +0 -0
  84. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/exceptions.py +0 -0
  85. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/__init__.py +0 -0
  86. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/base_runner.py +0 -0
  87. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/local/__init__.py +0 -0
  88. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/local/get_local_config.py +0 -0
  89. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/local/runner.py +0 -0
  90. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/__init__.py +0 -0
  91. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/_batching.py +0 -0
  92. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/_job_states.py +0 -0
  93. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/_slurm_config.py +0 -0
  94. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/get_slurm_config.py +0 -0
  95. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/remote.py +0 -0
  96. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_common/utils_executors.py +0 -0
  97. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_ssh/__init__.py +0 -0
  98. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_sudo/__init__.py +0 -0
  99. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/executors/slurm_sudo/_subprocess_run_as_user.py +0 -0
  100. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/filenames.py +0 -0
  101. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/run_subprocess.py +0 -0
  102. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/set_start_and_last_task_index.py +0 -0
  103. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/shutdown.py +0 -0
  104. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/__init__.py +0 -0
  105. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/_local.py +0 -0
  106. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/_slurm_ssh.py +0 -0
  107. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/_slurm_sudo.py +0 -0
  108. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/db_tools.py +0 -0
  109. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/deduplicate_list.py +0 -0
  110. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/merge_outputs.py +0 -0
  111. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/runner.py +0 -0
  112. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/runner_functions.py +0 -0
  113. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/runner_functions_low_level.py +0 -0
  114. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/submit_workflow.py +0 -0
  115. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/v2/task_interface.py +0 -0
  116. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/runner/versions.py +0 -0
  117. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/__init__.py +0 -0
  118. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/_filter_validators.py +0 -0
  119. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/_validators.py +0 -0
  120. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/user.py +0 -0
  121. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/user_group.py +0 -0
  122. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/user_settings.py +0 -0
  123. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/__init__.py +0 -0
  124. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/accounting.py +0 -0
  125. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/dataset.py +0 -0
  126. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/dumps.py +0 -0
  127. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/history.py +0 -0
  128. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/job.py +0 -0
  129. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/manifest.py +0 -0
  130. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/project.py +0 -0
  131. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/status_legacy.py +0 -0
  132. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/task.py +0 -0
  133. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/task_collection.py +0 -0
  134. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/task_group.py +0 -0
  135. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/workflow.py +0 -0
  136. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/schemas/v2/workflowtask.py +0 -0
  137. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/security/__init__.py +0 -0
  138. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/security/signup_email.py +0 -0
  139. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/app/user_settings.py +0 -0
  140. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/config.py +0 -0
  141. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/data_migrations/README.md +0 -0
  142. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/data_migrations/tools.py +0 -0
  143. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/gunicorn_fractal.py +0 -0
  144. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/images/__init__.py +0 -0
  145. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/images/models.py +0 -0
  146. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/images/tools.py +0 -0
  147. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/logger.py +0 -0
  148. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/main.py +0 -0
  149. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/env.py +0 -0
  150. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/naming_convention.py +0 -0
  151. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/034a469ec2eb_task_groups.py +0 -0
  152. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
  153. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +0 -0
  154. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +0 -0
  155. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/316140ff7ee1_remove_usersettings_cache_dir.py +0 -0
  156. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +0 -0
  157. {fractal_server-2.14.0a21 → 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.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
  159. {fractal_server-2.14.0a21 → 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.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  161. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
  162. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  163. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
  164. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  165. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +0 -0
  166. {fractal_server-2.14.0a21 → 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.0a21 → 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.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
  169. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  170. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +0 -0
  171. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +0 -0
  172. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
  173. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  174. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +0 -0
  175. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +0 -0
  176. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +0 -0
  177. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
  178. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +0 -0
  179. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +0 -0
  180. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  181. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +0 -0
  182. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
  183. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  184. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +0 -0
  185. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/py.typed +0 -0
  186. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/ssh/__init__.py +0 -0
  187. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/string_tools.py +0 -0
  188. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/syringe.py +0 -0
  189. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/__init__.py +0 -0
  190. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/utils.py +0 -0
  191. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/__init__.py +0 -0
  192. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/__init__.py +0 -0
  193. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/_utils.py +0 -0
  194. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/collect.py +0 -0
  195. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/deactivate.py +0 -0
  196. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/local/reactivate.py +0 -0
  197. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/__init__.py +0 -0
  198. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/_utils.py +0 -0
  199. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/collect.py +0 -0
  200. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/deactivate.py +0 -0
  201. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/ssh/reactivate.py +0 -0
  202. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/1_create_venv.sh +0 -0
  203. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/2_pip_install.sh +0 -0
  204. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/3_pip_freeze.sh +0 -0
  205. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/4_pip_show.sh +0 -0
  206. {fractal_server-2.14.0a21 → 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.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +0 -0
  208. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_background.py +0 -0
  209. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_database.py +0 -0
  210. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_package_names.py +0 -0
  211. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_python_interpreter.py +0 -0
  212. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/tasks/v2/utils_templates.py +0 -0
  213. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/urls.py +0 -0
  214. {fractal_server-2.14.0a21 → fractal_server-2.14.0a23}/fractal_server/utils.py +0 -0
  215. {fractal_server-2.14.0a21 → 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.0a21
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"
@@ -1,3 +1,4 @@
1
+ from copy import deepcopy
1
2
  from typing import Any
2
3
  from typing import Optional
3
4
 
@@ -92,7 +93,7 @@ async def get_workflow_tasks_statuses(
92
93
  db=db,
93
94
  )
94
95
 
95
- response = {}
96
+ response: dict[int, dict[str, int | str] | None] = {}
96
97
  for wftask in workflow.task_list:
97
98
  res = await db.execute(
98
99
  select(HistoryRun)
@@ -130,7 +131,18 @@ async def get_workflow_tasks_statuses(
130
131
  f"num_{target_status.value}_images"
131
132
  ] = num_images
132
133
 
133
- return JSONResponse(content=response, status_code=200)
134
+ new_response = deepcopy(response)
135
+ for key, value in response.items():
136
+ if value is not None:
137
+ num_total_images = sum(
138
+ value[f"num_{target_status.value}_images"]
139
+ for target_status in HistoryUnitStatus
140
+ )
141
+ if num_total_images > value["num_available_images"]:
142
+ value["num_available_images"] = None
143
+ new_response[key] = value
144
+
145
+ return JSONResponse(content=new_response, status_code=200)
134
146
 
135
147
 
136
148
  @router.get("/project/{project_id}/status/run/")
@@ -11,8 +11,8 @@ built-in `tarfile` library has to do with performance issues we observed
11
11
  when handling files which were just created within a SLURM job, and in the
12
12
  context of a CephFS filesystem.
13
13
  """
14
- import shutil
15
14
  import sys
15
+ import time
16
16
  from pathlib import Path
17
17
 
18
18
  from fractal_server.app.runner.run_subprocess import run_subprocess
@@ -20,48 +20,66 @@ from fractal_server.logger import get_logger
20
20
  from fractal_server.logger import set_logger
21
21
 
22
22
 
23
- def copy_subfolder(src: Path, dest: Path, logger_name: str):
23
+ def _copy_subfolder(src: Path, dest: Path, logger_name: str):
24
+ t_start = time.perf_counter()
24
25
  cmd_cp = f"cp -r {src.as_posix()} {dest.as_posix()}"
25
26
  logger = get_logger(logger_name=logger_name)
26
27
  logger.debug(f"{cmd_cp=}")
27
28
  res = run_subprocess(cmd=cmd_cp, logger_name=logger_name)
29
+ elapsed = time.perf_counter() - t_start
30
+ logger.debug(f"[_copy_subfolder] END {elapsed=} s ({dest.as_posix()})")
28
31
  return res
29
32
 
30
33
 
31
- def create_tar_archive(
32
- tarfile_path: Path,
34
+ def _create_tar_archive(
35
+ tarfile_path: str,
33
36
  subfolder_path_tmp_copy: Path,
34
37
  logger_name: str,
35
- remote_to_local: bool,
38
+ filelist_path: str | None,
36
39
  ):
37
40
  logger = get_logger(logger_name)
38
-
39
- if remote_to_local:
40
- exclude_options = "--exclude *sbatch --exclude *_in_*.pickle "
41
+ logger.debug(f"[_create_tar_archive] START ({tarfile_path})")
42
+ t_start = time.perf_counter()
43
+
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
+ )
41
50
  else:
42
- 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}")
43
58
 
44
- cmd_tar = (
45
- f"tar czf {tarfile_path} "
46
- f"{exclude_options} "
47
- f"--directory={subfolder_path_tmp_copy.as_posix()} "
48
- "."
49
- )
50
- logger.debug(f"cmd tar:\n{cmd_tar}")
51
59
  run_subprocess(cmd=cmd_tar, logger_name=logger_name, allow_char="*")
60
+ elapsed = time.perf_counter() - t_start
61
+ logger.debug(f"[_create_tar_archive] END {elapsed=} s ({tarfile_path})")
52
62
 
53
63
 
54
- def remove_temp_subfolder(subfolder_path_tmp_copy: Path, logger_name: str):
64
+ def _remove_temp_subfolder(subfolder_path_tmp_copy: Path, logger_name: str):
55
65
  logger = get_logger(logger_name)
66
+ t_start = time.perf_counter()
56
67
  try:
57
- logger.debug(f"Now remove {subfolder_path_tmp_copy}")
58
- shutil.rmtree(subfolder_path_tmp_copy)
68
+ cmd_rm = f"rm -rf {subfolder_path_tmp_copy}"
69
+ logger.debug(f"cmd rm:\n{cmd_rm}")
70
+ run_subprocess(cmd=cmd_rm, logger_name=logger_name, allow_char="*")
59
71
  except Exception as e:
60
- logger.debug(f"ERROR during shutil.rmtree: {e}")
72
+ logger.debug(f"ERROR during {cmd_rm}: {e}")
73
+ elapsed = time.perf_counter() - t_start
74
+ logger.debug(
75
+ f"[_remove_temp_subfolder] END {elapsed=} s "
76
+ f"({subfolder_path_tmp_copy=})"
77
+ )
61
78
 
62
79
 
63
80
  def compress_folder(
64
- subfolder_path: Path, remote_to_local: bool = False
81
+ subfolder_path: Path,
82
+ filelist_path: str | None,
65
83
  ) -> str:
66
84
  """
67
85
  Compress e.g. `/path/archive` into `/path/archive.tar.gz`
@@ -91,14 +109,16 @@ def compress_folder(
91
109
  subfolder_path.parent / f"{subfolder_path.name}_copy"
92
110
  )
93
111
  try:
94
- copy_subfolder(
95
- subfolder_path, subfolder_path_tmp_copy, logger_name=logger_name
112
+ _copy_subfolder(
113
+ subfolder_path,
114
+ subfolder_path_tmp_copy,
115
+ logger_name=logger_name,
96
116
  )
97
- create_tar_archive(
117
+ _create_tar_archive(
98
118
  tarfile_path,
99
119
  subfolder_path_tmp_copy,
100
120
  logger_name=logger_name,
101
- remote_to_local=remote_to_local,
121
+ filelist_path=filelist_path,
102
122
  )
103
123
  return tarfile_path
104
124
 
@@ -107,7 +127,9 @@ def compress_folder(
107
127
  sys.exit(1)
108
128
 
109
129
  finally:
110
- remove_temp_subfolder(subfolder_path_tmp_copy, logger_name=logger_name)
130
+ _remove_temp_subfolder(
131
+ subfolder_path_tmp_copy, logger_name=logger_name
132
+ )
111
133
 
112
134
 
113
135
  def main(sys_argv: list[str]):
@@ -115,15 +137,21 @@ def main(sys_argv: list[str]):
115
137
  help_msg = (
116
138
  "Expected use:\n"
117
139
  "python -m fractal_server.app.runner.compress_folder "
118
- "path/to/folder [--remote-to-local]\n"
140
+ "path/to/folder [--filelist /path/to/filelist]\n"
119
141
  )
120
142
  num_args = len(sys_argv[1:])
121
143
  if num_args == 0:
122
144
  sys.exit(f"Invalid argument.\n{help_msg}\nProvided: {sys_argv[1:]=}")
123
145
  elif num_args == 1:
124
- compress_folder(subfolder_path=Path(sys_argv[1]))
125
- elif num_args == 2 and sys_argv[2] == "--remote-to-local":
126
- 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
+ )
127
155
  else:
128
156
  sys.exit(f"Invalid argument.\n{help_msg}\nProvided: {sys_argv[1:]=}")
129
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