fractal-server 2.9.0a4__tar.gz → 2.9.0a6__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.0a4 → fractal_server-2.9.0a6}/PKG-INFO +1 -1
  2. fractal_server-2.9.0a6/fractal_server/__init__.py +1 -0
  3. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v2/task_group.py +10 -3
  4. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/admin/v1.py +13 -11
  5. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/admin/v2/job.py +13 -5
  6. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/admin/v2/task_group.py +22 -1
  7. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/admin/v2/task_group_lifecycle.py +12 -3
  8. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v1/project.py +7 -19
  9. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +40 -0
  10. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/submit.py +6 -25
  11. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/task_collection.py +2 -2
  12. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/task_group.py +3 -0
  13. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/task_group_lifecycle.py +7 -0
  14. fractal_server-2.9.0a6/fractal_server/app/routes/aux/__init__.py +20 -0
  15. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/ssh/executor.py +8 -31
  16. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/__init__.py +1 -0
  17. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/task_group.py +28 -1
  18. fractal_server-2.9.0a4/fractal_server/migrations/versions/3082479ac4ea_taskgroup_activity_and_venv_info_to_.py → fractal_server-2.9.0a6/fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +10 -6
  19. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/ssh/_fabric.py +85 -65
  20. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/pyproject.toml +2 -2
  21. fractal_server-2.9.0a4/fractal_server/__init__.py +0 -1
  22. fractal_server-2.9.0a4/fractal_server/app/routes/aux/_timestamp.py +0 -18
  23. fractal_server-2.9.0a4/fractal_server/tasks/v2/__init__.py +0 -0
  24. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/LICENSE +0 -0
  25. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/README.md +0 -0
  26. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/__main__.py +0 -0
  27. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/alembic.ini +0 -0
  28. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/__init__.py +0 -0
  29. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/db/__init__.py +0 -0
  30. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/__init__.py +0 -0
  31. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/linkusergroup.py +0 -0
  32. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/linkuserproject.py +0 -0
  33. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/security.py +0 -0
  34. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/user_settings.py +0 -0
  35. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v1/__init__.py +0 -0
  36. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v1/dataset.py +0 -0
  37. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v1/job.py +0 -0
  38. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v1/project.py +0 -0
  39. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v1/state.py +0 -0
  40. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v1/task.py +0 -0
  41. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v1/workflow.py +0 -0
  42. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v2/__init__.py +0 -0
  43. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v2/dataset.py +0 -0
  44. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v2/job.py +0 -0
  45. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v2/project.py +0 -0
  46. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v2/task.py +0 -0
  47. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v2/workflow.py +0 -0
  48. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/models/v2/workflowtask.py +0 -0
  49. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/__init__.py +0 -0
  50. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/admin/__init__.py +0 -0
  51. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/admin/v2/__init__.py +0 -0
  52. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/admin/v2/project.py +0 -0
  53. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/admin/v2/task.py +0 -0
  54. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/__init__.py +0 -0
  55. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v1/__init__.py +0 -0
  56. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v1/_aux_functions.py +0 -0
  57. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v1/dataset.py +0 -0
  58. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v1/job.py +0 -0
  59. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v1/task.py +0 -0
  60. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v1/task_collection.py +0 -0
  61. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v1/workflow.py +0 -0
  62. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v1/workflowtask.py +0 -0
  63. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/__init__.py +0 -0
  64. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/_aux_functions.py +0 -0
  65. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/_aux_functions_tasks.py +0 -0
  66. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/dataset.py +0 -0
  67. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/images.py +0 -0
  68. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/job.py +0 -0
  69. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/project.py +0 -0
  70. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/status.py +0 -0
  71. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/task.py +0 -0
  72. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/task_collection_custom.py +0 -0
  73. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/workflow.py +0 -0
  74. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/workflow_import.py +0 -0
  75. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/api/v2/workflowtask.py +0 -0
  76. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/auth/__init__.py +0 -0
  77. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/auth/_aux_auth.py +0 -0
  78. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/auth/current_user.py +0 -0
  79. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/auth/group.py +0 -0
  80. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/auth/login.py +0 -0
  81. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/auth/oauth.py +0 -0
  82. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/auth/register.py +0 -0
  83. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/auth/router.py +0 -0
  84. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/auth/users.py +0 -0
  85. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/aux/_job.py +0 -0
  86. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/aux/_runner.py +0 -0
  87. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/routes/aux/validate_user_settings.py +0 -0
  88. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/.gitignore +0 -0
  89. {fractal_server-2.9.0a4/fractal_server/app/routes/aux → fractal_server-2.9.0a6/fractal_server/app/runner}/__init__.py +0 -0
  90. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/async_wrap.py +0 -0
  91. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/components.py +0 -0
  92. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/compress_folder.py +0 -0
  93. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/exceptions.py +0 -0
  94. {fractal_server-2.9.0a4/fractal_server/app/runner → fractal_server-2.9.0a6/fractal_server/app/runner/executors}/__init__.py +0 -0
  95. {fractal_server-2.9.0a4/fractal_server/app/runner/executors → fractal_server-2.9.0a6/fractal_server/app/runner/executors/slurm}/__init__.py +0 -0
  96. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/_batching.py +0 -0
  97. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/_slurm_config.py +0 -0
  98. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/remote.py +0 -0
  99. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/ssh/__init__.py +0 -0
  100. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py +0 -0
  101. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py +0 -0
  102. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/sudo/__init__.py +0 -0
  103. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py +0 -0
  104. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py +0 -0
  105. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/sudo/_subprocess_run_as_user.py +0 -0
  106. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/executors/slurm/sudo/executor.py +0 -0
  107. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/extract_archive.py +0 -0
  108. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/filenames.py +0 -0
  109. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/run_subprocess.py +0 -0
  110. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/set_start_and_last_task_index.py +0 -0
  111. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/shutdown.py +0 -0
  112. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/task_files.py +0 -0
  113. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/__init__.py +0 -0
  114. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/_common.py +0 -0
  115. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/_local/__init__.py +0 -0
  116. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/_local/_local_config.py +0 -0
  117. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/_local/_submit_setup.py +0 -0
  118. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/_local/executor.py +0 -0
  119. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/_slurm/__init__.py +0 -0
  120. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/_slurm/_submit_setup.py +0 -0
  121. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/_slurm/get_slurm_config.py +0 -0
  122. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/common.py +0 -0
  123. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v1/handle_failed_job.py +0 -0
  124. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/__init__.py +0 -0
  125. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_local/__init__.py +0 -0
  126. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_local/_local_config.py +0 -0
  127. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_local/_submit_setup.py +0 -0
  128. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_local/executor.py +0 -0
  129. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_local_experimental/__init__.py +0 -0
  130. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_local_experimental/_local_config.py +0 -0
  131. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_local_experimental/_submit_setup.py +0 -0
  132. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_local_experimental/executor.py +0 -0
  133. {fractal_server-2.9.0a4/fractal_server/app/runner/executors/slurm → fractal_server-2.9.0a6/fractal_server/app/runner/v2/_slurm_common}/__init__.py +0 -0
  134. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py +0 -0
  135. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_slurm_ssh/__init__.py +0 -0
  136. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_slurm_ssh/_submit_setup.py +0 -0
  137. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_slurm_sudo/__init__.py +0 -0
  138. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/_slurm_sudo/_submit_setup.py +0 -0
  139. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/deduplicate_list.py +0 -0
  140. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/handle_failed_job.py +0 -0
  141. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/merge_outputs.py +0 -0
  142. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/runner.py +0 -0
  143. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/runner_functions.py +0 -0
  144. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/runner_functions_low_level.py +0 -0
  145. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/v2/task_interface.py +0 -0
  146. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/runner/versions.py +0 -0
  147. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/__init__.py +0 -0
  148. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/_validators.py +0 -0
  149. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/user.py +0 -0
  150. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/user_group.py +0 -0
  151. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/user_settings.py +0 -0
  152. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/__init__.py +0 -0
  153. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/applyworkflow.py +0 -0
  154. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/dataset.py +0 -0
  155. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/dumps.py +0 -0
  156. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/manifest.py +0 -0
  157. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/project.py +0 -0
  158. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/state.py +0 -0
  159. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/task.py +0 -0
  160. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/task_collection.py +0 -0
  161. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v1/workflow.py +0 -0
  162. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/dataset.py +0 -0
  163. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/dumps.py +0 -0
  164. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/job.py +0 -0
  165. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/manifest.py +0 -0
  166. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/project.py +0 -0
  167. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/status.py +0 -0
  168. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/task.py +0 -0
  169. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/task_collection.py +0 -0
  170. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/workflow.py +0 -0
  171. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/schemas/v2/workflowtask.py +0 -0
  172. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/security/__init__.py +0 -0
  173. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/app/user_settings.py +0 -0
  174. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/config.py +0 -0
  175. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/data_migrations/README.md +0 -0
  176. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/data_migrations/tools.py +0 -0
  177. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/gunicorn_fractal.py +0 -0
  178. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/images/__init__.py +0 -0
  179. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/images/models.py +0 -0
  180. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/images/tools.py +0 -0
  181. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/logger.py +0 -0
  182. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/main.py +0 -0
  183. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/README +0 -0
  184. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/env.py +0 -0
  185. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/naming_convention.py +0 -0
  186. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/script.py.mako +0 -0
  187. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/034a469ec2eb_task_groups.py +0 -0
  188. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
  189. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +0 -0
  190. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
  191. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
  192. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +0 -0
  193. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  194. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
  195. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  196. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
  197. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  198. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +0 -0
  199. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
  200. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +0 -0
  201. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
  202. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  203. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +0 -0
  204. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
  205. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  206. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
  207. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +0 -0
  208. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  209. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
  210. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  211. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/py.typed +0 -0
  212. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/ssh/__init__.py +0 -0
  213. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/string_tools.py +0 -0
  214. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/syringe.py +0 -0
  215. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/__init__.py +0 -0
  216. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/utils.py +0 -0
  217. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v1/_TaskCollectPip.py +0 -0
  218. {fractal_server-2.9.0a4/fractal_server/app/runner/v2/_slurm_common → fractal_server-2.9.0a6/fractal_server/tasks/v1}/__init__.py +0 -0
  219. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v1/background_operations.py +0 -0
  220. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v1/endpoint_operations.py +0 -0
  221. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v1/get_collection_data.py +0 -0
  222. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v1/utils.py +0 -0
  223. {fractal_server-2.9.0a4/fractal_server/tasks/v1 → fractal_server-2.9.0a6/fractal_server/tasks/v2}/__init__.py +0 -0
  224. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/local/__init__.py +0 -0
  225. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/local/_utils.py +0 -0
  226. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/local/collect.py +0 -0
  227. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/local/deactivate.py +0 -0
  228. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/local/reactivate.py +0 -0
  229. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/ssh/__init__.py +0 -0
  230. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/ssh/_utils.py +0 -0
  231. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/ssh/collect.py +0 -0
  232. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/ssh/deactivate.py +0 -0
  233. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/ssh/reactivate.py +0 -0
  234. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/templates/1_create_venv.sh +0 -0
  235. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/templates/2_pip_install.sh +0 -0
  236. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/templates/3_pip_freeze.sh +0 -0
  237. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/templates/4_pip_show.sh +0 -0
  238. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +0 -0
  239. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +0 -0
  240. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/utils_background.py +0 -0
  241. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/utils_database.py +0 -0
  242. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/utils_package_names.py +0 -0
  243. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/utils_python_interpreter.py +0 -0
  244. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/tasks/v2/utils_templates.py +0 -0
  245. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/urls.py +0 -0
  246. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/fractal_server/utils.py +0 -0
  247. {fractal_server-2.9.0a4 → fractal_server-2.9.0a6}/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.0a4
3
+ Version: 2.9.0a6
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.0a6"
@@ -1,4 +1,5 @@
1
1
  from datetime import datetime
2
+ from datetime import timezone
2
3
  from typing import Optional
3
4
 
4
5
  from sqlalchemy import Column
@@ -48,9 +49,15 @@ class TaskGroupV2(SQLModel, table=True):
48
49
  default_factory=get_timestamp,
49
50
  sa_column=Column(DateTime(timezone=True), nullable=False),
50
51
  )
51
- timestamp_last_used: Optional[datetime] = Field(
52
- default=None,
53
- sa_column=Column(DateTime(timezone=True), nullable=True),
52
+ timestamp_last_used: datetime = Field(
53
+ default_factory=get_timestamp,
54
+ sa_column=Column(
55
+ DateTime(timezone=True),
56
+ nullable=False,
57
+ server_default=(
58
+ datetime(2024, 11, 20, tzinfo=timezone.utc).isoformat()
59
+ ),
60
+ ),
54
61
  )
55
62
 
56
63
  @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)
@@ -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()
@@ -13,6 +13,9 @@ from fractal_server.app.models.v2 import TaskGroupActivityV2
13
13
  from fractal_server.app.routes.api.v2._aux_functions_task_lifecycle import (
14
14
  check_no_ongoing_activity,
15
15
  )
16
+ from fractal_server.app.routes.api.v2._aux_functions_task_lifecycle import (
17
+ check_no_submitted_job,
18
+ )
16
19
  from fractal_server.app.routes.api.v2._aux_functions_tasks import (
17
20
  _get_task_group_or_404,
18
21
  )
@@ -58,9 +61,6 @@ async def deactivate_task_group(
58
61
  task_group_id=task_group_id, db=db
59
62
  )
60
63
 
61
- # Check no other activity is ongoing
62
- await check_no_ongoing_activity(task_group_id=task_group_id, db=db)
63
-
64
64
  # Check that task-group is active
65
65
  if not task_group.active:
66
66
  raise HTTPException(
@@ -70,6 +70,12 @@ async def deactivate_task_group(
70
70
  ),
71
71
  )
72
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
+
73
79
  # Shortcut for task-group with origin="other"
74
80
  if task_group.origin == TaskGroupV2OriginEnum.OTHER:
75
81
  task_group.active = False
@@ -178,6 +184,9 @@ async def reactivate_task_group(
178
184
  # Check no other activity is ongoing
179
185
  await check_no_ongoing_activity(task_group_id=task_group_id, db=db)
180
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
+
181
190
  # Shortcut for task-group with origin="other"
182
191
  if task_group.origin == TaskGroupV2OriginEnum.OTHER:
183
192
  task_group.active = True
@@ -1,5 +1,5 @@
1
+ import json
1
2
  import os
2
- from datetime import datetime
3
3
  from datetime import timedelta
4
4
  from datetime import timezone
5
5
  from typing import Optional
@@ -50,10 +50,6 @@ router = APIRouter()
50
50
  logger = set_logger(__name__)
51
51
 
52
52
 
53
- def _encode_as_utc(dt: datetime):
54
- return dt.replace(tzinfo=timezone.utc).isoformat()
55
-
56
-
57
53
  @router.get("/", response_model=list[ProjectReadV1])
58
54
  async def get_list_project(
59
55
  user: UserOAuth = Depends(current_active_user),
@@ -393,33 +389,25 @@ async def apply_workflow(
393
389
  workflow_id=workflow_id,
394
390
  user_email=user.email,
395
391
  input_dataset_dump=dict(
396
- **input_dataset.model_dump(
397
- exclude={"resource_list", "history", "timestamp_created"}
392
+ **json.loads(
393
+ input_dataset.json(exclude={"resource_list", "history"})
398
394
  ),
399
- timestamp_created=_encode_as_utc(input_dataset.timestamp_created),
400
395
  resource_list=[
401
396
  resource.model_dump()
402
397
  for resource in input_dataset.resource_list
403
398
  ],
404
399
  ),
405
400
  output_dataset_dump=dict(
406
- **output_dataset.model_dump(
407
- exclude={"resource_list", "history", "timestamp_created"}
401
+ **json.loads(
402
+ output_dataset.json(exclude={"resource_list", "history"})
408
403
  ),
409
- timestamp_created=_encode_as_utc(output_dataset.timestamp_created),
410
404
  resource_list=[
411
405
  resource.model_dump()
412
406
  for resource in output_dataset.resource_list
413
407
  ],
414
408
  ),
415
- workflow_dump=dict(
416
- **workflow.model_dump(exclude={"task_list", "timestamp_created"}),
417
- timestamp_created=_encode_as_utc(workflow.timestamp_created),
418
- ),
419
- project_dump=dict(
420
- **project.model_dump(exclude={"user_list", "timestamp_created"}),
421
- timestamp_created=_encode_as_utc(project.timestamp_created),
422
- ),
409
+ workflow_dump=json.loads(workflow.json(exclude={"task_list"})),
410
+ project_dump=json.loads(project.json(exclude={"user_list"})),
423
411
  **apply_workflow.dict(),
424
412
  )
425
413
 
@@ -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
+ )
@@ -1,6 +1,5 @@
1
+ import json
1
2
  import os
2
- from datetime import datetime
3
- from datetime import timezone
4
3
  from pathlib import Path
5
4
  from typing import Optional
6
5
 
@@ -15,7 +14,6 @@ from sqlmodel import select
15
14
  from .....config import get_settings
16
15
  from .....logger import set_logger
17
16
  from .....syringe import Inject
18
- from .....utils import get_timestamp
19
17
  from ....db import AsyncSession
20
18
  from ....db import get_async_db
21
19
  from ....models.v2 import JobV2
@@ -38,10 +36,6 @@ from fractal_server.app.routes.api.v2._aux_functions_tasks import (
38
36
  from fractal_server.app.routes.auth import current_active_verified_user
39
37
 
40
38
 
41
- def _encode_as_utc(dt: datetime):
42
- return dt.replace(tzinfo=timezone.utc).isoformat()
43
-
44
-
45
39
  router = APIRouter()
46
40
  logger = set_logger(__name__)
47
41
 
@@ -62,8 +56,6 @@ async def apply_workflow(
62
56
  db: AsyncSession = Depends(get_async_db),
63
57
  ) -> Optional[JobReadV2]:
64
58
 
65
- now = get_timestamp()
66
-
67
59
  # Remove non-submitted V2 jobs from the app state when the list grows
68
60
  # beyond a threshold
69
61
  settings = Inject(get_settings)
@@ -167,20 +159,9 @@ async def apply_workflow(
167
159
  dataset_id=dataset_id,
168
160
  workflow_id=workflow_id,
169
161
  user_email=user.email,
170
- dataset_dump=dict(
171
- **dataset.model_dump(
172
- exclude={"images", "history", "timestamp_created"}
173
- ),
174
- timestamp_created=_encode_as_utc(dataset.timestamp_created),
175
- ),
176
- workflow_dump=dict(
177
- **workflow.model_dump(exclude={"task_list", "timestamp_created"}),
178
- timestamp_created=_encode_as_utc(workflow.timestamp_created),
179
- ),
180
- project_dump=dict(
181
- **project.model_dump(exclude={"user_list", "timestamp_created"}),
182
- timestamp_created=_encode_as_utc(project.timestamp_created),
183
- ),
162
+ dataset_dump=json.loads(dataset.json(exclude={"images", "history"})),
163
+ workflow_dump=json.loads(workflow.json(exclude={"task_list"})),
164
+ project_dump=json.loads(project.json(exclude={"user_list"})),
184
165
  **job_create.dict(),
185
166
  )
186
167
 
@@ -194,12 +175,12 @@ async def apply_workflow(
194
175
  )
195
176
  used_task_groups = res.scalars().all()
196
177
  for used_task_group in used_task_groups:
197
- used_task_group.timestamp_last_used = now
178
+ used_task_group.timestamp_last_used = job.start_timestamp
198
179
  db.add(used_task_group)
199
180
  await db.commit()
200
181
 
201
182
  # Define server-side job directory
202
- timestamp_string = now.strftime("%Y%m%d_%H%M%S")
183
+ timestamp_string = job.start_timestamp.strftime("%Y%m%d_%H%M%S")
203
184
  WORKFLOW_DIR_LOCAL = settings.FRACTAL_RUNNER_WORKING_BASE_DIR / (
204
185
  f"proj_v2_{project_id:07d}_wf_{workflow_id:07d}_job_{job.id:07d}"
205
186
  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
+ )
@@ -26,7 +26,6 @@ from typing import Sequence
26
26
 
27
27
  import cloudpickle
28
28
  from cfut import SlurmExecutor
29
- from paramiko.ssh_exception import NoValidConnectionsError
30
29
 
31
30
  from ....filenames import SHUTDOWN_FILENAME
32
31
  from ....task_files import get_task_file_paths
@@ -409,15 +408,7 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
409
408
  args=fun_args,
410
409
  kwargs=fun_kwargs,
411
410
  )
412
- try:
413
- self._put_subfolder_sftp(jobs=[job])
414
- except NoValidConnectionsError as e:
415
- logger.error("NoValidConnectionError")
416
- logger.error(f"{str(e)=}")
417
- logger.error(f"{e.errors=}")
418
- for err in e.errors:
419
- logger.error(f"{str(err)}")
420
- raise e
411
+ self._put_subfolder_sftp(jobs=[job])
421
412
  future, job_id_str = self._submit_job(job)
422
413
  self.wait_thread.wait(job_id=job_id_str)
423
414
  return future
@@ -559,16 +550,7 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
559
550
  current_component_index += batch_size
560
551
  logger.debug("[map] Job preparation - END")
561
552
 
562
- try:
563
- self._put_subfolder_sftp(jobs=jobs_to_submit)
564
- except NoValidConnectionsError as e:
565
- logger.error("NoValidConnectionError")
566
- logger.error(f"{str(e)=}")
567
- logger.error(f"{e.errors=}")
568
- for err in e.errors:
569
- logger.error(f"{str(err)}")
570
-
571
- raise e
553
+ self._put_subfolder_sftp(jobs=jobs_to_submit)
572
554
 
573
555
  # Construct list of futures (one per SLURM job, i.e. one per batch)
574
556
  # FIXME SSH: we may create a single `_submit_many_jobs` method to
@@ -1073,16 +1055,7 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
1073
1055
  self.jobs_empty_cond.notify_all()
1074
1056
 
1075
1057
  # Fetch subfolder from remote host
1076
- try:
1077
- self._get_subfolder_sftp(jobs=jobs)
1078
- except NoValidConnectionsError as e:
1079
- logger.error("NoValidConnectionError")
1080
- logger.error(f"{str(e)=}")
1081
- logger.error(f"{e.errors=}")
1082
- for err in e.errors:
1083
- logger.error(f"{str(err)}")
1084
-
1085
- raise e
1058
+ self._get_subfolder_sftp(jobs=jobs)
1086
1059
 
1087
1060
  # First round of checking whether all output files exist
1088
1061
  missing_out_paths = []
@@ -1527,7 +1500,11 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
1527
1500
  logger.info("[FractalSlurmSSHExecutor.ssh_handshake] START")
1528
1501
  cmd = f"{self.python_remote} -m fractal_server.app.runner.versions"
1529
1502
  stdout = self.fractal_ssh.run_command(cmd=cmd)
1530
- remote_versions = json.loads(stdout.strip("\n"))
1503
+ try:
1504
+ remote_versions = json.loads(stdout.strip("\n"))
1505
+ except json.decoder.JSONDecodeError as e:
1506
+ logger.error("Fractal server versions not available")
1507
+ raise e
1531
1508
 
1532
1509
  # Check compatibility with local versions
1533
1510
  local_versions = get_versions()
@@ -29,6 +29,7 @@ from .task_group import TaskGroupActivityActionV2 # noqa F401
29
29
  from .task_group import TaskGroupActivityStatusV2 # noqa F401
30
30
  from .task_group import TaskGroupActivityV2Read # noqa F401
31
31
  from .task_group import TaskGroupCreateV2 # noqa F401
32
+ from .task_group import TaskGroupCreateV2Strict # noqa F401
32
33
  from .task_group import TaskGroupReadV2 # noqa F401
33
34
  from .task_group import TaskGroupUpdateV2 # noqa F401
34
35
  from .task_group import TaskGroupV2OriginEnum # noqa F401