fractal-server 2.9.0a3__tar.gz → 2.9.0a5__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 (247) hide show
  1. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/PKG-INFO +1 -1
  2. fractal_server-2.9.0a5/fractal_server/__init__.py +1 -0
  3. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v2/task_group.py +2 -2
  4. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/admin/v1.py +13 -11
  5. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/admin/v2/__init__.py +4 -0
  6. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/admin/v2/job.py +13 -5
  7. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/admin/v2/task_group.py +22 -1
  8. fractal_server-2.9.0a5/fractal_server/app/routes/admin/v2/task_group_lifecycle.py +269 -0
  9. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +40 -0
  10. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/submit.py +2 -5
  11. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/task_collection.py +2 -2
  12. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/task_group.py +3 -0
  13. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/task_group_lifecycle.py +7 -0
  14. fractal_server-2.9.0a5/fractal_server/app/routes/aux/__init__.py +20 -0
  15. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/ssh/executor.py +3 -30
  16. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/__init__.py +1 -0
  17. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/task_group.py +28 -1
  18. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/3082479ac4ea_taskgroup_activity_and_venv_info_to_.py +1 -1
  19. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/ssh/_fabric.py +85 -65
  20. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/ssh/collect.py +1 -1
  21. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/pyproject.toml +2 -2
  22. fractal_server-2.9.0a3/fractal_server/__init__.py +0 -1
  23. fractal_server-2.9.0a3/fractal_server/app/routes/aux/_timestamp.py +0 -18
  24. fractal_server-2.9.0a3/fractal_server/tasks/v2/__init__.py +0 -0
  25. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/LICENSE +0 -0
  26. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/README.md +0 -0
  27. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/__main__.py +0 -0
  28. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/alembic.ini +0 -0
  29. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/__init__.py +0 -0
  30. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/db/__init__.py +0 -0
  31. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/__init__.py +0 -0
  32. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/linkusergroup.py +0 -0
  33. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/linkuserproject.py +0 -0
  34. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/security.py +0 -0
  35. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/user_settings.py +0 -0
  36. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v1/__init__.py +0 -0
  37. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v1/dataset.py +0 -0
  38. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v1/job.py +0 -0
  39. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v1/project.py +0 -0
  40. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v1/state.py +0 -0
  41. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v1/task.py +0 -0
  42. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v1/workflow.py +0 -0
  43. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v2/__init__.py +0 -0
  44. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v2/dataset.py +0 -0
  45. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v2/job.py +0 -0
  46. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v2/project.py +0 -0
  47. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v2/task.py +0 -0
  48. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v2/workflow.py +0 -0
  49. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/models/v2/workflowtask.py +0 -0
  50. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/__init__.py +0 -0
  51. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/admin/__init__.py +0 -0
  52. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/admin/v2/project.py +0 -0
  53. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/admin/v2/task.py +0 -0
  54. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/__init__.py +0 -0
  55. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v1/__init__.py +0 -0
  56. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v1/_aux_functions.py +0 -0
  57. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v1/dataset.py +0 -0
  58. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v1/job.py +0 -0
  59. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v1/project.py +0 -0
  60. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v1/task.py +0 -0
  61. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v1/task_collection.py +0 -0
  62. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v1/workflow.py +0 -0
  63. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v1/workflowtask.py +0 -0
  64. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/__init__.py +0 -0
  65. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/_aux_functions.py +0 -0
  66. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/_aux_functions_tasks.py +0 -0
  67. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/dataset.py +0 -0
  68. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/images.py +0 -0
  69. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/job.py +0 -0
  70. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/project.py +0 -0
  71. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/status.py +0 -0
  72. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/task.py +0 -0
  73. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/task_collection_custom.py +0 -0
  74. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/workflow.py +0 -0
  75. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/workflow_import.py +0 -0
  76. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/api/v2/workflowtask.py +0 -0
  77. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/auth/__init__.py +0 -0
  78. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/auth/_aux_auth.py +0 -0
  79. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/auth/current_user.py +0 -0
  80. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/auth/group.py +0 -0
  81. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/auth/login.py +0 -0
  82. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/auth/oauth.py +0 -0
  83. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/auth/register.py +0 -0
  84. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/auth/router.py +0 -0
  85. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/auth/users.py +0 -0
  86. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/aux/_job.py +0 -0
  87. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/aux/_runner.py +0 -0
  88. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/routes/aux/validate_user_settings.py +0 -0
  89. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/.gitignore +0 -0
  90. {fractal_server-2.9.0a3/fractal_server/app/routes/aux → fractal_server-2.9.0a5/fractal_server/app/runner}/__init__.py +0 -0
  91. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/async_wrap.py +0 -0
  92. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/components.py +0 -0
  93. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/compress_folder.py +0 -0
  94. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/exceptions.py +0 -0
  95. {fractal_server-2.9.0a3/fractal_server/app/runner → fractal_server-2.9.0a5/fractal_server/app/runner/executors}/__init__.py +0 -0
  96. {fractal_server-2.9.0a3/fractal_server/app/runner/executors → fractal_server-2.9.0a5/fractal_server/app/runner/executors/slurm}/__init__.py +0 -0
  97. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/_batching.py +0 -0
  98. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/_slurm_config.py +0 -0
  99. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/remote.py +0 -0
  100. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/ssh/__init__.py +0 -0
  101. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py +0 -0
  102. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py +0 -0
  103. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/sudo/__init__.py +0 -0
  104. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py +0 -0
  105. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py +0 -0
  106. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/sudo/_subprocess_run_as_user.py +0 -0
  107. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/executors/slurm/sudo/executor.py +0 -0
  108. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/extract_archive.py +0 -0
  109. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/filenames.py +0 -0
  110. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/run_subprocess.py +0 -0
  111. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/set_start_and_last_task_index.py +0 -0
  112. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/shutdown.py +0 -0
  113. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/task_files.py +0 -0
  114. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/__init__.py +0 -0
  115. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/_common.py +0 -0
  116. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/_local/__init__.py +0 -0
  117. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/_local/_local_config.py +0 -0
  118. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/_local/_submit_setup.py +0 -0
  119. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/_local/executor.py +0 -0
  120. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/_slurm/__init__.py +0 -0
  121. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/_slurm/_submit_setup.py +0 -0
  122. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/_slurm/get_slurm_config.py +0 -0
  123. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/common.py +0 -0
  124. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v1/handle_failed_job.py +0 -0
  125. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/__init__.py +0 -0
  126. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_local/__init__.py +0 -0
  127. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_local/_local_config.py +0 -0
  128. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_local/_submit_setup.py +0 -0
  129. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_local/executor.py +0 -0
  130. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_local_experimental/__init__.py +0 -0
  131. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_local_experimental/_local_config.py +0 -0
  132. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_local_experimental/_submit_setup.py +0 -0
  133. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_local_experimental/executor.py +0 -0
  134. {fractal_server-2.9.0a3/fractal_server/app/runner/executors/slurm → fractal_server-2.9.0a5/fractal_server/app/runner/v2/_slurm_common}/__init__.py +0 -0
  135. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py +0 -0
  136. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_slurm_ssh/__init__.py +0 -0
  137. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_slurm_ssh/_submit_setup.py +0 -0
  138. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_slurm_sudo/__init__.py +0 -0
  139. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/_slurm_sudo/_submit_setup.py +0 -0
  140. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/deduplicate_list.py +0 -0
  141. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/handle_failed_job.py +0 -0
  142. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/merge_outputs.py +0 -0
  143. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/runner.py +0 -0
  144. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/runner_functions.py +0 -0
  145. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/runner_functions_low_level.py +0 -0
  146. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/v2/task_interface.py +0 -0
  147. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/runner/versions.py +0 -0
  148. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/__init__.py +0 -0
  149. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/_validators.py +0 -0
  150. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/user.py +0 -0
  151. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/user_group.py +0 -0
  152. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/user_settings.py +0 -0
  153. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/__init__.py +0 -0
  154. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/applyworkflow.py +0 -0
  155. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/dataset.py +0 -0
  156. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/dumps.py +0 -0
  157. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/manifest.py +0 -0
  158. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/project.py +0 -0
  159. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/state.py +0 -0
  160. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/task.py +0 -0
  161. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/task_collection.py +0 -0
  162. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v1/workflow.py +0 -0
  163. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/dataset.py +0 -0
  164. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/dumps.py +0 -0
  165. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/job.py +0 -0
  166. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/manifest.py +0 -0
  167. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/project.py +0 -0
  168. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/status.py +0 -0
  169. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/task.py +0 -0
  170. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/task_collection.py +0 -0
  171. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/workflow.py +0 -0
  172. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/schemas/v2/workflowtask.py +0 -0
  173. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/security/__init__.py +0 -0
  174. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/app/user_settings.py +0 -0
  175. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/config.py +0 -0
  176. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/data_migrations/README.md +0 -0
  177. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/data_migrations/tools.py +0 -0
  178. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/gunicorn_fractal.py +0 -0
  179. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/images/__init__.py +0 -0
  180. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/images/models.py +0 -0
  181. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/images/tools.py +0 -0
  182. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/logger.py +0 -0
  183. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/main.py +0 -0
  184. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/README +0 -0
  185. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/env.py +0 -0
  186. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/naming_convention.py +0 -0
  187. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/script.py.mako +0 -0
  188. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/034a469ec2eb_task_groups.py +0 -0
  189. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
  190. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +0 -0
  191. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
  192. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
  193. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +0 -0
  194. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  195. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
  196. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  197. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
  198. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  199. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +0 -0
  200. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
  201. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +0 -0
  202. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
  203. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  204. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +0 -0
  205. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
  206. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  207. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
  208. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +0 -0
  209. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  210. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
  211. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  212. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/py.typed +0 -0
  213. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/ssh/__init__.py +0 -0
  214. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/string_tools.py +0 -0
  215. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/syringe.py +0 -0
  216. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/__init__.py +0 -0
  217. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/utils.py +0 -0
  218. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v1/_TaskCollectPip.py +0 -0
  219. {fractal_server-2.9.0a3/fractal_server/app/runner/v2/_slurm_common → fractal_server-2.9.0a5/fractal_server/tasks/v1}/__init__.py +0 -0
  220. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v1/background_operations.py +0 -0
  221. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v1/endpoint_operations.py +0 -0
  222. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v1/get_collection_data.py +0 -0
  223. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v1/utils.py +0 -0
  224. {fractal_server-2.9.0a3/fractal_server/tasks/v1 → fractal_server-2.9.0a5/fractal_server/tasks/v2}/__init__.py +0 -0
  225. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/local/__init__.py +0 -0
  226. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/local/_utils.py +0 -0
  227. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/local/collect.py +0 -0
  228. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/local/deactivate.py +0 -0
  229. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/local/reactivate.py +0 -0
  230. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/ssh/__init__.py +0 -0
  231. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/ssh/_utils.py +0 -0
  232. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/ssh/deactivate.py +0 -0
  233. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/ssh/reactivate.py +0 -0
  234. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/templates/1_create_venv.sh +0 -0
  235. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/templates/2_pip_install.sh +0 -0
  236. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/templates/3_pip_freeze.sh +0 -0
  237. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/templates/4_pip_show.sh +0 -0
  238. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +0 -0
  239. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +0 -0
  240. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/utils_background.py +0 -0
  241. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/utils_database.py +0 -0
  242. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/utils_package_names.py +0 -0
  243. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/utils_python_interpreter.py +0 -0
  244. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/tasks/v2/utils_templates.py +0 -0
  245. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/urls.py +0 -0
  246. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/utils.py +0 -0
  247. {fractal_server-2.9.0a3 → fractal_server-2.9.0a5}/fractal_server/zip_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fractal-server
3
- Version: 2.9.0a3
3
+ Version: 2.9.0a5
4
4
  Summary: Server component of the Fractal analytics platform
5
5
  Home-page: https://github.com/fractal-analytics-platform/fractal-server
6
6
  License: BSD-3-Clause
@@ -0,0 +1 @@
1
+ __VERSION__ = "2.9.0a5"
@@ -49,8 +49,8 @@ class TaskGroupV2(SQLModel, table=True):
49
49
  sa_column=Column(DateTime(timezone=True), nullable=False),
50
50
  )
51
51
  timestamp_last_used: Optional[datetime] = Field(
52
- default=None,
53
- sa_column=Column(DateTime(timezone=True), nullable=True),
52
+ default_factory=get_timestamp,
53
+ sa_column=Column(DateTime(timezone=True), nullable=False),
54
54
  )
55
55
 
56
56
  @property
@@ -33,7 +33,7 @@ from ..aux._job import _write_shutdown_file
33
33
  from ..aux._runner import _check_shutdown_is_supported
34
34
  from fractal_server.app.models import UserOAuth
35
35
  from fractal_server.app.routes.auth import current_active_superuser
36
- from fractal_server.app.routes.aux._timestamp import _convert_to_db_timestamp
36
+ from fractal_server.app.routes.aux import _raise_if_naive_datetime
37
37
 
38
38
  router_admin_v1 = APIRouter()
39
39
 
@@ -54,6 +54,7 @@ async def view_project(
54
54
  id: If not `None`, select a given `project.id`.
55
55
  user_id: If not `None`, select a given `project.user_id`.
56
56
  """
57
+ _raise_if_naive_datetime(timestamp_created_min, timestamp_created_max)
57
58
 
58
59
  stm = select(Project)
59
60
 
@@ -63,10 +64,8 @@ async def view_project(
63
64
  if user_id is not None:
64
65
  stm = stm.where(Project.user_list.any(UserOAuth.id == user_id))
65
66
  if timestamp_created_min is not None:
66
- timestamp_created_min = _convert_to_db_timestamp(timestamp_created_min)
67
67
  stm = stm.where(Project.timestamp_created >= timestamp_created_min)
68
68
  if timestamp_created_max is not None:
69
- timestamp_created_max = _convert_to_db_timestamp(timestamp_created_max)
70
69
  stm = stm.where(Project.timestamp_created <= timestamp_created_max)
71
70
 
72
71
  res = await db.execute(stm)
@@ -96,6 +95,8 @@ async def view_workflow(
96
95
  name_contains: If not `None`, select workflows such that their
97
96
  `name` attribute contains `name_contains` (case-insensitive).
98
97
  """
98
+ _raise_if_naive_datetime(timestamp_created_min, timestamp_created_max)
99
+
99
100
  stm = select(Workflow)
100
101
 
101
102
  if user_id is not None:
@@ -112,10 +113,8 @@ async def view_workflow(
112
113
  func.lower(Workflow.name).contains(name_contains.lower())
113
114
  )
114
115
  if timestamp_created_min is not None:
115
- timestamp_created_min = _convert_to_db_timestamp(timestamp_created_min)
116
116
  stm = stm.where(Workflow.timestamp_created >= timestamp_created_min)
117
117
  if timestamp_created_max is not None:
118
- timestamp_created_max = _convert_to_db_timestamp(timestamp_created_max)
119
118
  stm = stm.where(Workflow.timestamp_created <= timestamp_created_max)
120
119
 
121
120
  res = await db.execute(stm)
@@ -147,6 +146,8 @@ async def view_dataset(
147
146
  `name` attribute contains `name_contains` (case-insensitive).
148
147
  type: If not `None`, select a given `dataset.type`.
149
148
  """
149
+ _raise_if_naive_datetime(timestamp_created_min, timestamp_created_max)
150
+
150
151
  stm = select(Dataset)
151
152
 
152
153
  if user_id is not None:
@@ -165,10 +166,8 @@ async def view_dataset(
165
166
  if type is not None:
166
167
  stm = stm.where(Dataset.type == type)
167
168
  if timestamp_created_min is not None:
168
- timestamp_created_min = _convert_to_db_timestamp(timestamp_created_min)
169
169
  stm = stm.where(Dataset.timestamp_created >= timestamp_created_min)
170
170
  if timestamp_created_max is not None:
171
- timestamp_created_max = _convert_to_db_timestamp(timestamp_created_max)
172
171
  stm = stm.where(Dataset.timestamp_created <= timestamp_created_max)
173
172
 
174
173
  res = await db.execute(stm)
@@ -218,6 +217,13 @@ async def view_job(
218
217
  log: If `True`, include `job.log`, if `False`
219
218
  `job.log` is set to `None`.
220
219
  """
220
+ _raise_if_naive_datetime(
221
+ start_timestamp_min,
222
+ start_timestamp_max,
223
+ end_timestamp_min,
224
+ end_timestamp_max,
225
+ )
226
+
221
227
  stm = select(ApplyWorkflow)
222
228
 
223
229
  if id is not None:
@@ -237,16 +243,12 @@ async def view_job(
237
243
  if status is not None:
238
244
  stm = stm.where(ApplyWorkflow.status == status)
239
245
  if start_timestamp_min is not None:
240
- start_timestamp_min = _convert_to_db_timestamp(start_timestamp_min)
241
246
  stm = stm.where(ApplyWorkflow.start_timestamp >= start_timestamp_min)
242
247
  if start_timestamp_max is not None:
243
- start_timestamp_max = _convert_to_db_timestamp(start_timestamp_max)
244
248
  stm = stm.where(ApplyWorkflow.start_timestamp <= start_timestamp_max)
245
249
  if end_timestamp_min is not None:
246
- end_timestamp_min = _convert_to_db_timestamp(end_timestamp_min)
247
250
  stm = stm.where(ApplyWorkflow.end_timestamp >= end_timestamp_min)
248
251
  if end_timestamp_max is not None:
249
- end_timestamp_max = _convert_to_db_timestamp(end_timestamp_max)
250
252
  stm = stm.where(ApplyWorkflow.end_timestamp <= end_timestamp_max)
251
253
 
252
254
  res = await db.execute(stm)
@@ -7,6 +7,7 @@ from .job import router as job_router
7
7
  from .project import router as project_router
8
8
  from .task import router as task_router
9
9
  from .task_group import router as task_group_router
10
+ from .task_group_lifecycle import router as task_group_lifecycle_router
10
11
 
11
12
  router_admin_v2 = APIRouter()
12
13
 
@@ -14,3 +15,6 @@ router_admin_v2.include_router(job_router, prefix="/job")
14
15
  router_admin_v2.include_router(project_router, prefix="/project")
15
16
  router_admin_v2.include_router(task_router, prefix="/task")
16
17
  router_admin_v2.include_router(task_group_router, prefix="/task-group")
18
+ router_admin_v2.include_router(
19
+ task_group_lifecycle_router, prefix="/task-group"
20
+ )
@@ -16,9 +16,9 @@ from fractal_server.app.models import UserOAuth
16
16
  from fractal_server.app.models.v2 import JobV2
17
17
  from fractal_server.app.models.v2 import ProjectV2
18
18
  from fractal_server.app.routes.auth import current_active_superuser
19
+ from fractal_server.app.routes.aux import _raise_if_naive_datetime
19
20
  from fractal_server.app.routes.aux._job import _write_shutdown_file
20
21
  from fractal_server.app.routes.aux._runner import _check_shutdown_is_supported
21
- from fractal_server.app.routes.aux._timestamp import _convert_to_db_timestamp
22
22
  from fractal_server.app.runner.filenames import WORKFLOW_LOG_FILENAME
23
23
  from fractal_server.app.schemas.v2 import JobReadV2
24
24
  from fractal_server.app.schemas.v2 import JobStatusTypeV2
@@ -66,6 +66,14 @@ async def view_job(
66
66
  log: If `True`, include `job.log`, if `False`
67
67
  `job.log` is set to `None`.
68
68
  """
69
+
70
+ _raise_if_naive_datetime(
71
+ start_timestamp_min,
72
+ start_timestamp_max,
73
+ end_timestamp_min,
74
+ end_timestamp_max,
75
+ )
76
+
69
77
  stm = select(JobV2)
70
78
 
71
79
  if id is not None:
@@ -83,16 +91,16 @@ async def view_job(
83
91
  if status is not None:
84
92
  stm = stm.where(JobV2.status == status)
85
93
  if start_timestamp_min is not None:
86
- start_timestamp_min = _convert_to_db_timestamp(start_timestamp_min)
94
+ start_timestamp_min = start_timestamp_min
87
95
  stm = stm.where(JobV2.start_timestamp >= start_timestamp_min)
88
96
  if start_timestamp_max is not None:
89
- start_timestamp_max = _convert_to_db_timestamp(start_timestamp_max)
97
+ start_timestamp_max = start_timestamp_max
90
98
  stm = stm.where(JobV2.start_timestamp <= start_timestamp_max)
91
99
  if end_timestamp_min is not None:
92
- end_timestamp_min = _convert_to_db_timestamp(end_timestamp_min)
100
+ end_timestamp_min = end_timestamp_min
93
101
  stm = stm.where(JobV2.end_timestamp >= end_timestamp_min)
94
102
  if end_timestamp_max is not None:
95
- end_timestamp_max = _convert_to_db_timestamp(end_timestamp_max)
103
+ end_timestamp_max = end_timestamp_max
96
104
  stm = stm.where(JobV2.end_timestamp <= end_timestamp_max)
97
105
 
98
106
  res = await db.execute(stm)
@@ -20,6 +20,7 @@ from fractal_server.app.routes.auth import current_active_superuser
20
20
  from fractal_server.app.routes.auth._aux_auth import (
21
21
  _verify_user_belongs_to_group,
22
22
  )
23
+ from fractal_server.app.routes.aux import _raise_if_naive_datetime
23
24
  from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
24
25
  from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
25
26
  from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
@@ -46,6 +47,8 @@ async def get_task_group_activity_list(
46
47
  db: AsyncSession = Depends(get_async_db),
47
48
  ) -> list[TaskGroupActivityV2Read]:
48
49
 
50
+ _raise_if_naive_datetime(timestamp_started_min)
51
+
49
52
  stm = select(TaskGroupActivityV2)
50
53
  if task_group_activity_id is not None:
51
54
  stm = stm.where(TaskGroupActivityV2.id == task_group_activity_id)
@@ -93,16 +96,26 @@ async def query_task_group_list(
93
96
  active: Optional[bool] = None,
94
97
  pkg_name: Optional[str] = None,
95
98
  origin: Optional[TaskGroupV2OriginEnum] = None,
99
+ timestamp_last_used_min: Optional[datetime] = None,
100
+ timestamp_last_used_max: Optional[datetime] = None,
96
101
  user: UserOAuth = Depends(current_active_superuser),
97
102
  db: AsyncSession = Depends(get_async_db),
98
103
  ) -> list[TaskGroupReadV2]:
99
104
 
100
105
  stm = select(TaskGroupV2)
101
106
 
107
+ _raise_if_naive_datetime(
108
+ timestamp_last_used_max,
109
+ timestamp_last_used_min,
110
+ )
111
+
102
112
  if user_group_id is not None and private is True:
103
113
  raise HTTPException(
104
114
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
105
- detail=f"Cannot set `user_group_id` with {private=}",
115
+ detail=(
116
+ "Cannot get task groups with both "
117
+ f"{user_group_id=} and {private=}."
118
+ ),
106
119
  )
107
120
  if user_id is not None:
108
121
  stm = stm.where(TaskGroupV2.user_id == user_id)
@@ -122,6 +135,14 @@ async def query_task_group_list(
122
135
  stm = stm.where(TaskGroupV2.origin == origin)
123
136
  if pkg_name is not None:
124
137
  stm = stm.where(TaskGroupV2.pkg_name.icontains(pkg_name))
138
+ if timestamp_last_used_min is not None:
139
+ stm = stm.where(
140
+ TaskGroupV2.timestamp_last_used >= timestamp_last_used_min
141
+ )
142
+ if timestamp_last_used_max is not None:
143
+ stm = stm.where(
144
+ TaskGroupV2.timestamp_last_used <= timestamp_last_used_max
145
+ )
125
146
 
126
147
  res = await db.execute(stm)
127
148
  task_groups_list = res.scalars().all()
@@ -0,0 +1,269 @@
1
+ from fastapi import APIRouter
2
+ from fastapi import BackgroundTasks
3
+ from fastapi import Depends
4
+ from fastapi import HTTPException
5
+ from fastapi import Request
6
+ from fastapi import Response
7
+ from fastapi import status
8
+
9
+ from fractal_server.app.db import AsyncSession
10
+ from fractal_server.app.db import get_async_db
11
+ from fractal_server.app.models import UserOAuth
12
+ from fractal_server.app.models.v2 import TaskGroupActivityV2
13
+ from fractal_server.app.routes.api.v2._aux_functions_task_lifecycle import (
14
+ check_no_ongoing_activity,
15
+ )
16
+ from fractal_server.app.routes.api.v2._aux_functions_task_lifecycle import (
17
+ check_no_submitted_job,
18
+ )
19
+ from fractal_server.app.routes.api.v2._aux_functions_tasks import (
20
+ _get_task_group_or_404,
21
+ )
22
+ from fractal_server.app.routes.auth import current_active_superuser
23
+ from fractal_server.app.routes.aux.validate_user_settings import (
24
+ validate_user_settings,
25
+ )
26
+ from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
27
+ from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
28
+ from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
29
+ from fractal_server.app.schemas.v2 import TaskGroupReadV2
30
+ from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
31
+ from fractal_server.config import get_settings
32
+ from fractal_server.logger import set_logger
33
+ from fractal_server.syringe import Inject
34
+ from fractal_server.tasks.v2.local import deactivate_local
35
+ from fractal_server.tasks.v2.local import reactivate_local
36
+ from fractal_server.tasks.v2.ssh import deactivate_ssh
37
+ from fractal_server.tasks.v2.ssh import reactivate_ssh
38
+ from fractal_server.utils import get_timestamp
39
+
40
+ router = APIRouter()
41
+
42
+ logger = set_logger(__name__)
43
+
44
+
45
+ @router.post(
46
+ "/{task_group_id}/deactivate/",
47
+ response_model=TaskGroupActivityV2Read,
48
+ )
49
+ async def deactivate_task_group(
50
+ task_group_id: int,
51
+ background_tasks: BackgroundTasks,
52
+ response: Response,
53
+ request: Request,
54
+ superuser: UserOAuth = Depends(current_active_superuser),
55
+ db: AsyncSession = Depends(get_async_db),
56
+ ) -> TaskGroupReadV2:
57
+ """
58
+ Deactivate task-group venv
59
+ """
60
+ task_group = await _get_task_group_or_404(
61
+ task_group_id=task_group_id, db=db
62
+ )
63
+
64
+ # Check that task-group is active
65
+ if not task_group.active:
66
+ raise HTTPException(
67
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
68
+ detail=(
69
+ f"Cannot deactivate a task group with {task_group.active=}."
70
+ ),
71
+ )
72
+
73
+ # Check no other activity is ongoing
74
+ await check_no_ongoing_activity(task_group_id=task_group_id, db=db)
75
+
76
+ # Check no submitted jobs use tasks from this task group
77
+ await check_no_submitted_job(task_group_id=task_group.id, db=db)
78
+
79
+ # Shortcut for task-group with origin="other"
80
+ if task_group.origin == TaskGroupV2OriginEnum.OTHER:
81
+ task_group.active = False
82
+ task_group_activity = TaskGroupActivityV2(
83
+ user_id=task_group.user_id,
84
+ taskgroupv2_id=task_group.id,
85
+ status=TaskGroupActivityStatusV2.OK,
86
+ action=TaskGroupActivityActionV2.DEACTIVATE,
87
+ pkg_name=task_group.pkg_name,
88
+ version=(task_group.version or "N/A"),
89
+ log=(
90
+ f"Task group has {task_group.origin=}, set "
91
+ "task_group.active to False and exit."
92
+ ),
93
+ timestamp_started=get_timestamp(),
94
+ timestamp_ended=get_timestamp(),
95
+ )
96
+ db.add(task_group)
97
+ db.add(task_group_activity)
98
+ await db.commit()
99
+ response.status_code = status.HTTP_202_ACCEPTED
100
+ return task_group_activity
101
+
102
+ task_group_activity = TaskGroupActivityV2(
103
+ user_id=task_group.user_id,
104
+ taskgroupv2_id=task_group.id,
105
+ status=TaskGroupActivityStatusV2.PENDING,
106
+ action=TaskGroupActivityActionV2.DEACTIVATE,
107
+ pkg_name=task_group.pkg_name,
108
+ version=task_group.version,
109
+ timestamp_started=get_timestamp(),
110
+ )
111
+ task_group.active = False
112
+ db.add(task_group)
113
+ db.add(task_group_activity)
114
+ await db.commit()
115
+
116
+ # Submit background task
117
+ settings = Inject(get_settings)
118
+ if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
119
+ # Validate user settings (backend-specific)
120
+ user = await db.get(UserOAuth, task_group.user_id)
121
+ user_settings = await validate_user_settings(
122
+ user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
123
+ )
124
+ # User appropriate FractalSSH object
125
+ ssh_credentials = dict(
126
+ user=user_settings.ssh_username,
127
+ host=user_settings.ssh_host,
128
+ key_path=user_settings.ssh_private_key_path,
129
+ )
130
+ fractal_ssh_list = request.app.state.fractal_ssh_list
131
+ fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
132
+
133
+ background_tasks.add_task(
134
+ deactivate_ssh,
135
+ task_group_id=task_group.id,
136
+ task_group_activity_id=task_group_activity.id,
137
+ fractal_ssh=fractal_ssh,
138
+ tasks_base_dir=user_settings.ssh_tasks_dir,
139
+ )
140
+ else:
141
+ background_tasks.add_task(
142
+ deactivate_local,
143
+ task_group_id=task_group.id,
144
+ task_group_activity_id=task_group_activity.id,
145
+ )
146
+
147
+ logger.debug(
148
+ "Admin task group deactivation endpoint: start deactivate "
149
+ "and return task_group_activity"
150
+ )
151
+ response.status_code = status.HTTP_202_ACCEPTED
152
+ return task_group_activity
153
+
154
+
155
+ @router.post(
156
+ "/{task_group_id}/reactivate/",
157
+ response_model=TaskGroupActivityV2Read,
158
+ )
159
+ async def reactivate_task_group(
160
+ task_group_id: int,
161
+ background_tasks: BackgroundTasks,
162
+ response: Response,
163
+ request: Request,
164
+ superuser: UserOAuth = Depends(current_active_superuser),
165
+ db: AsyncSession = Depends(get_async_db),
166
+ ) -> TaskGroupReadV2:
167
+ """
168
+ Deactivate task-group venv
169
+ """
170
+
171
+ task_group = await _get_task_group_or_404(
172
+ task_group_id=task_group_id, db=db
173
+ )
174
+
175
+ # Check that task-group is not active
176
+ if task_group.active:
177
+ raise HTTPException(
178
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
179
+ detail=(
180
+ f"Cannot reactivate a task group with {task_group.active=}."
181
+ ),
182
+ )
183
+
184
+ # Check no other activity is ongoing
185
+ await check_no_ongoing_activity(task_group_id=task_group_id, db=db)
186
+
187
+ # Check no submitted jobs use tasks from this task group
188
+ await check_no_submitted_job(task_group_id=task_group.id, db=db)
189
+
190
+ # Shortcut for task-group with origin="other"
191
+ if task_group.origin == TaskGroupV2OriginEnum.OTHER:
192
+ task_group.active = True
193
+ task_group_activity = TaskGroupActivityV2(
194
+ user_id=task_group.user_id,
195
+ taskgroupv2_id=task_group.id,
196
+ status=TaskGroupActivityStatusV2.OK,
197
+ action=TaskGroupActivityActionV2.REACTIVATE,
198
+ pkg_name=task_group.pkg_name,
199
+ version=(task_group.version or "N/A"),
200
+ log=(
201
+ f"Task group has {task_group.origin=}, set "
202
+ "task_group.active to True and exit."
203
+ ),
204
+ timestamp_started=get_timestamp(),
205
+ timestamp_ended=get_timestamp(),
206
+ )
207
+ db.add(task_group)
208
+ db.add(task_group_activity)
209
+ await db.commit()
210
+ response.status_code = status.HTTP_202_ACCEPTED
211
+ return task_group_activity
212
+
213
+ if task_group.pip_freeze is None:
214
+ raise HTTPException(
215
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
216
+ detail=(
217
+ "Cannot reactivate a task group with "
218
+ f"{task_group.pip_freeze=}."
219
+ ),
220
+ )
221
+
222
+ task_group_activity = TaskGroupActivityV2(
223
+ user_id=task_group.user_id,
224
+ taskgroupv2_id=task_group.id,
225
+ status=TaskGroupActivityStatusV2.PENDING,
226
+ action=TaskGroupActivityActionV2.REACTIVATE,
227
+ pkg_name=task_group.pkg_name,
228
+ version=task_group.version,
229
+ timestamp_started=get_timestamp(),
230
+ )
231
+ db.add(task_group_activity)
232
+ await db.commit()
233
+
234
+ # Submit background task
235
+ settings = Inject(get_settings)
236
+ if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
237
+ # Validate user settings (backend-specific)
238
+ user = await db.get(UserOAuth, task_group.user_id)
239
+ user_settings = await validate_user_settings(
240
+ user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
241
+ )
242
+ # Use appropriate FractalSSH object
243
+ ssh_credentials = dict(
244
+ user=user_settings.ssh_username,
245
+ host=user_settings.ssh_host,
246
+ key_path=user_settings.ssh_private_key_path,
247
+ )
248
+ fractal_ssh_list = request.app.state.fractal_ssh_list
249
+ fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
250
+
251
+ background_tasks.add_task(
252
+ reactivate_ssh,
253
+ task_group_id=task_group.id,
254
+ task_group_activity_id=task_group_activity.id,
255
+ fractal_ssh=fractal_ssh,
256
+ tasks_base_dir=user_settings.ssh_tasks_dir,
257
+ )
258
+ else:
259
+ background_tasks.add_task(
260
+ reactivate_local,
261
+ task_group_id=task_group.id,
262
+ task_group_activity_id=task_group_activity.id,
263
+ )
264
+ logger.debug(
265
+ "Admin task group reactivation endpoint: start reactivate "
266
+ "and return task_group_activity"
267
+ )
268
+ response.status_code = status.HTTP_202_ACCEPTED
269
+ return task_group_activity
@@ -4,10 +4,16 @@ from fastapi import HTTPException
4
4
  from fastapi import status
5
5
  from httpx import AsyncClient
6
6
  from httpx import TimeoutException
7
+ from sqlmodel import func
7
8
  from sqlmodel import select
8
9
 
9
10
  from fractal_server.app.db import AsyncSession
11
+ from fractal_server.app.models.v2 import JobV2
10
12
  from fractal_server.app.models.v2 import TaskGroupActivityV2
13
+ from fractal_server.app.models.v2 import TaskV2
14
+ from fractal_server.app.models.v2 import WorkflowTaskV2
15
+ from fractal_server.app.models.v2 import WorkflowV2
16
+ from fractal_server.app.schemas.v2 import JobStatusTypeV2
11
17
  from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
12
18
  from fractal_server.logger import set_logger
13
19
 
@@ -165,3 +171,37 @@ async def check_no_ongoing_activity(
165
171
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
166
172
  detail=msg,
167
173
  )
174
+
175
+
176
+ async def check_no_submitted_job(
177
+ *,
178
+ task_group_id: int,
179
+ db: AsyncSession,
180
+ ) -> None:
181
+ """
182
+ Find submitted jobs which include tasks from a given task group.
183
+
184
+ Arguments:
185
+ task_id_list: List of TaskV2 IDs
186
+ db: Database session
187
+ """
188
+ stm = (
189
+ select(func.count(JobV2.id))
190
+ .join(WorkflowV2, JobV2.workflow_id == WorkflowV2.id)
191
+ .join(WorkflowTaskV2, WorkflowTaskV2.workflow_id == WorkflowV2.id)
192
+ .join(TaskV2, WorkflowTaskV2.task_id == TaskV2.id)
193
+ .where(WorkflowTaskV2.order >= JobV2.first_task_index)
194
+ .where(WorkflowTaskV2.order <= JobV2.last_task_index)
195
+ .where(JobV2.status == JobStatusTypeV2.SUBMITTED)
196
+ .where(TaskV2.taskgroupv2_id == task_group_id)
197
+ )
198
+ res = await db.execute(stm)
199
+ num_submitted_jobs = res.scalar()
200
+ if num_submitted_jobs > 0:
201
+ raise HTTPException(
202
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
203
+ detail=(
204
+ f"Cannot act on task group because {num_submitted_jobs} "
205
+ "submitted jobs use its tasks."
206
+ ),
207
+ )
@@ -15,7 +15,6 @@ from sqlmodel import select
15
15
  from .....config import get_settings
16
16
  from .....logger import set_logger
17
17
  from .....syringe import Inject
18
- from .....utils import get_timestamp
19
18
  from ....db import AsyncSession
20
19
  from ....db import get_async_db
21
20
  from ....models.v2 import JobV2
@@ -62,8 +61,6 @@ async def apply_workflow(
62
61
  db: AsyncSession = Depends(get_async_db),
63
62
  ) -> Optional[JobReadV2]:
64
63
 
65
- now = get_timestamp()
66
-
67
64
  # Remove non-submitted V2 jobs from the app state when the list grows
68
65
  # beyond a threshold
69
66
  settings = Inject(get_settings)
@@ -194,12 +191,12 @@ async def apply_workflow(
194
191
  )
195
192
  used_task_groups = res.scalars().all()
196
193
  for used_task_group in used_task_groups:
197
- used_task_group.timestamp_last_used = now
194
+ used_task_group.timestamp_last_used = job.start_timestamp
198
195
  db.add(used_task_group)
199
196
  await db.commit()
200
197
 
201
198
  # Define server-side job directory
202
- timestamp_string = now.strftime("%Y%m%d_%H%M%S")
199
+ timestamp_string = job.start_timestamp.strftime("%Y%m%d_%H%M%S")
203
200
  WORKFLOW_DIR_LOCAL = settings.FRACTAL_RUNNER_WORKING_BASE_DIR / (
204
201
  f"proj_v2_{project_id:07d}_wf_{workflow_id:07d}_job_{job.id:07d}"
205
202
  f"_{timestamp_string}"
@@ -21,7 +21,7 @@ from ....models.v2 import TaskGroupV2
21
21
  from ....schemas.v2 import TaskCollectPipV2
22
22
  from ....schemas.v2 import TaskGroupActivityStatusV2
23
23
  from ....schemas.v2 import TaskGroupActivityV2Read
24
- from ....schemas.v2 import TaskGroupCreateV2
24
+ from ....schemas.v2 import TaskGroupCreateV2Strict
25
25
  from ...aux.validate_user_settings import validate_user_settings
26
26
  from ._aux_functions_task_lifecycle import get_package_version_from_pypi
27
27
  from ._aux_functions_tasks import _get_valid_user_group_id
@@ -164,7 +164,7 @@ async def collect_tasks_pip(
164
164
 
165
165
  # Validate TaskGroupV2 attributes
166
166
  try:
167
- TaskGroupCreateV2(**task_group_attrs)
167
+ TaskGroupCreateV2Strict(**task_group_attrs)
168
168
  except ValidationError as e:
169
169
  raise HTTPException(
170
170
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
@@ -23,6 +23,7 @@ from fractal_server.app.routes.auth import current_active_user
23
23
  from fractal_server.app.routes.auth._aux_auth import (
24
24
  _verify_user_belongs_to_group,
25
25
  )
26
+ from fractal_server.app.routes.aux import _raise_if_naive_datetime
26
27
  from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
27
28
  from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
28
29
  from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
@@ -47,6 +48,8 @@ async def get_task_group_activity_list(
47
48
  db: AsyncSession = Depends(get_async_db),
48
49
  ) -> list[TaskGroupActivityV2Read]:
49
50
 
51
+ _raise_if_naive_datetime(timestamp_started_min)
52
+
50
53
  stm = select(TaskGroupActivityV2).where(
51
54
  TaskGroupActivityV2.user_id == user.id
52
55
  )
@@ -8,6 +8,7 @@ from fastapi import status
8
8
 
9
9
  from ...aux.validate_user_settings import validate_user_settings
10
10
  from ._aux_functions_task_lifecycle import check_no_ongoing_activity
11
+ from ._aux_functions_task_lifecycle import check_no_submitted_job
11
12
  from ._aux_functions_tasks import _get_task_group_full_access
12
13
  from fractal_server.app.db import AsyncSession
13
14
  from fractal_server.app.db import get_async_db
@@ -59,6 +60,9 @@ async def deactivate_task_group(
59
60
  # Check no other activity is ongoing
60
61
  await check_no_ongoing_activity(task_group_id=task_group_id, db=db)
61
62
 
63
+ # Check no submitted jobs use tasks from this task group
64
+ await check_no_submitted_job(task_group_id=task_group.id, db=db)
65
+
62
66
  # Check that task-group is active
63
67
  if not task_group.active:
64
68
  raise HTTPException(
@@ -181,6 +185,9 @@ async def reactivate_task_group(
181
185
  # Check no other activity is ongoing
182
186
  await check_no_ongoing_activity(task_group_id=task_group_id, db=db)
183
187
 
188
+ # Check no submitted jobs use tasks from this task group
189
+ await check_no_submitted_job(task_group_id=task_group.id, db=db)
190
+
184
191
  # Shortcut for task-group with origin="other"
185
192
  if task_group.origin == TaskGroupV2OriginEnum.OTHER:
186
193
  task_group.active = True
@@ -0,0 +1,20 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from fastapi import HTTPException
5
+ from fastapi import status
6
+
7
+
8
+ def _raise_if_naive_datetime(*timestamps: tuple[Optional[datetime]]) -> None:
9
+ """
10
+ Raise 422 if any not-null argument is a naive `datetime` object:
11
+ https://docs.python.org/3/library/datetime.html#determining-if-an-object-is-aware-or-naive
12
+ """
13
+ for timestamp in filter(None, timestamps):
14
+ if (timestamp.tzinfo is None) or (
15
+ timestamp.tzinfo.utcoffset(timestamp) is None
16
+ ):
17
+ raise HTTPException(
18
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
19
+ detail=f"{timestamp=} is naive. You must provide a timezone.",
20
+ )