fractal-server 2.14.0a6__tar.gz → 2.14.0a8__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 (217) hide show
  1. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/PKG-INFO +2 -2
  2. fractal_server-2.14.0a8/fractal_server/__init__.py +1 -0
  3. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/_aux_functions.py +35 -45
  4. fractal_server-2.14.0a8/fractal_server/app/routes/api/v2/_aux_functions_history.py +158 -0
  5. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/history.py +42 -71
  6. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/job.py +30 -0
  7. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/status_legacy.py +5 -5
  8. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/task.py +3 -10
  9. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/workflowtask.py +13 -2
  10. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/base_runner.py +53 -26
  11. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/local/runner.py +6 -5
  12. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_ssh/executor.py +3 -4
  13. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_sudo/runner.py +5 -60
  14. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/runner.py +68 -33
  15. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/runner_functions.py +256 -14
  16. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/__init__.py +7 -1
  17. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/dumps.py +20 -4
  18. fractal_server-2.14.0a8/fractal_server/app/schemas/v2/history.py +54 -0
  19. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/manifest.py +11 -0
  20. fractal_server-2.14.0a8/fractal_server/app/schemas/v2/status_legacy.py +35 -0
  21. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/task.py +32 -1
  22. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/workflowtask.py +2 -21
  23. fractal_server-2.14.0a8/fractal_server/tasks/v2/__init__.py +0 -0
  24. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/utils_database.py +1 -16
  25. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/pyproject.toml +2 -2
  26. fractal_server-2.14.0a6/fractal_server/__init__.py +0 -1
  27. fractal_server-2.14.0a6/fractal_server/app/history/__init__.py +0 -4
  28. fractal_server-2.14.0a6/fractal_server/app/history/image_updates.py +0 -124
  29. fractal_server-2.14.0a6/fractal_server/app/history/status_enum.py +0 -16
  30. fractal_server-2.14.0a6/fractal_server/app/routes/api/v2/_aux_functions_history.py +0 -49
  31. fractal_server-2.14.0a6/fractal_server/app/schemas/v2/status.py +0 -16
  32. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/LICENSE +0 -0
  33. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/README.md +0 -0
  34. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/__main__.py +0 -0
  35. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/alembic.ini +0 -0
  36. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/__init__.py +0 -0
  37. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/db/__init__.py +0 -0
  38. {fractal_server-2.14.0a6/fractal_server/app/routes → fractal_server-2.14.0a8/fractal_server/app/history}/__init__.py +0 -0
  39. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/__init__.py +0 -0
  40. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/linkusergroup.py +0 -0
  41. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/linkuserproject.py +0 -0
  42. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/security.py +0 -0
  43. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/user_settings.py +0 -0
  44. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/__init__.py +0 -0
  45. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/accounting.py +0 -0
  46. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/dataset.py +0 -0
  47. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/history.py +0 -0
  48. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/job.py +0 -0
  49. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/project.py +0 -0
  50. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/task.py +0 -0
  51. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/task_group.py +0 -0
  52. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/workflow.py +0 -0
  53. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/models/v2/workflowtask.py +0 -0
  54. {fractal_server-2.14.0a6/fractal_server/app/routes/admin → fractal_server-2.14.0a8/fractal_server/app/routes}/__init__.py +0 -0
  55. {fractal_server-2.14.0a6/fractal_server/app/routes/aux → fractal_server-2.14.0a8/fractal_server/app/routes/admin}/__init__.py +0 -0
  56. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/admin/v2/__init__.py +0 -0
  57. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/admin/v2/accounting.py +0 -0
  58. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/admin/v2/impersonate.py +0 -0
  59. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/admin/v2/job.py +0 -0
  60. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/admin/v2/project.py +0 -0
  61. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/admin/v2/task.py +0 -0
  62. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/admin/v2/task_group.py +0 -0
  63. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/admin/v2/task_group_lifecycle.py +0 -0
  64. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/__init__.py +0 -0
  65. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/__init__.py +0 -0
  66. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +0 -0
  67. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/_aux_functions_tasks.py +0 -0
  68. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/dataset.py +0 -0
  69. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/images.py +0 -0
  70. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/project.py +0 -0
  71. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/submit.py +0 -0
  72. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/task_collection.py +0 -0
  73. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/task_collection_custom.py +0 -0
  74. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/task_group.py +0 -0
  75. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/task_group_lifecycle.py +0 -0
  76. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/workflow.py +0 -0
  77. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/api/v2/workflow_import.py +0 -0
  78. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/auth/__init__.py +0 -0
  79. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/auth/_aux_auth.py +0 -0
  80. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/auth/current_user.py +0 -0
  81. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/auth/group.py +0 -0
  82. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/auth/login.py +0 -0
  83. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/auth/oauth.py +0 -0
  84. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/auth/register.py +0 -0
  85. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/auth/router.py +0 -0
  86. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/auth/users.py +0 -0
  87. {fractal_server-2.14.0a6/fractal_server/app/runner → fractal_server-2.14.0a8/fractal_server/app/routes/aux}/__init__.py +0 -0
  88. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/aux/_job.py +0 -0
  89. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/aux/_runner.py +0 -0
  90. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/aux/validate_user_settings.py +0 -0
  91. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/routes/pagination.py +0 -0
  92. {fractal_server-2.14.0a6/fractal_server/app/runner/executors → fractal_server-2.14.0a8/fractal_server/app/runner}/__init__.py +0 -0
  93. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/components.py +0 -0
  94. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/compress_folder.py +0 -0
  95. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/exceptions.py +0 -0
  96. {fractal_server-2.14.0a6/fractal_server/app/runner/executors/local → fractal_server-2.14.0a8/fractal_server/app/runner/executors}/__init__.py +0 -0
  97. {fractal_server-2.14.0a6/fractal_server/app/runner/executors/slurm_common → fractal_server-2.14.0a8/fractal_server/app/runner/executors/local}/__init__.py +0 -0
  98. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/local/_local_config.py +0 -0
  99. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/local/_submit_setup.py +0 -0
  100. {fractal_server-2.14.0a6/fractal_server/app/runner/executors/slurm_ssh → fractal_server-2.14.0a8/fractal_server/app/runner/executors/slurm_common}/__init__.py +0 -0
  101. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_common/_batching.py +0 -0
  102. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_common/_job_states.py +0 -0
  103. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_common/_slurm_config.py +0 -0
  104. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_common/_submit_setup.py +0 -0
  105. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_common/get_slurm_config.py +0 -0
  106. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_common/remote.py +0 -0
  107. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_common/utils_executors.py +0 -0
  108. {fractal_server-2.14.0a6/fractal_server/app/runner/executors/slurm_sudo → fractal_server-2.14.0a8/fractal_server/app/runner/executors/slurm_ssh}/__init__.py +0 -0
  109. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_ssh/_executor_wait_thread.py +0 -0
  110. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_ssh/_slurm_job.py +0 -0
  111. {fractal_server-2.14.0a6/fractal_server/tasks/v2 → fractal_server-2.14.0a8/fractal_server/app/runner/executors/slurm_sudo}/__init__.py +0 -0
  112. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_sudo/_check_jobs_status.py +0 -0
  113. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/executors/slurm_sudo/_subprocess_run_as_user.py +0 -0
  114. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/extract_archive.py +0 -0
  115. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/filenames.py +0 -0
  116. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/run_subprocess.py +0 -0
  117. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/set_start_and_last_task_index.py +0 -0
  118. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/shutdown.py +0 -0
  119. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/task_files.py +0 -0
  120. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/__init__.py +0 -0
  121. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/_db_tools.py +0 -0
  122. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/_local.py +0 -0
  123. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/_slurm_ssh.py +0 -0
  124. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/_slurm_sudo.py +0 -0
  125. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/deduplicate_list.py +0 -0
  126. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/merge_outputs.py +0 -0
  127. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/runner_functions_low_level.py +0 -0
  128. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/v2/task_interface.py +0 -0
  129. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/runner/versions.py +0 -0
  130. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/__init__.py +0 -0
  131. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/_filter_validators.py +0 -0
  132. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/_validators.py +0 -0
  133. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/user.py +0 -0
  134. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/user_group.py +0 -0
  135. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/user_settings.py +0 -0
  136. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/accounting.py +0 -0
  137. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/dataset.py +0 -0
  138. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/job.py +0 -0
  139. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/project.py +0 -0
  140. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/task_collection.py +0 -0
  141. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/task_group.py +0 -0
  142. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/schemas/v2/workflow.py +0 -0
  143. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/security/__init__.py +0 -0
  144. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/security/signup_email.py +0 -0
  145. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/app/user_settings.py +0 -0
  146. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/config.py +0 -0
  147. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/data_migrations/README.md +0 -0
  148. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/data_migrations/tools.py +0 -0
  149. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/gunicorn_fractal.py +0 -0
  150. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/images/__init__.py +0 -0
  151. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/images/models.py +0 -0
  152. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/images/tools.py +0 -0
  153. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/logger.py +0 -0
  154. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/main.py +0 -0
  155. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/env.py +0 -0
  156. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/naming_convention.py +0 -0
  157. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/034a469ec2eb_task_groups.py +0 -0
  158. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
  159. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +0 -0
  160. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +0 -0
  161. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/316140ff7ee1_remove_usersettings_cache_dir.py +0 -0
  162. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
  163. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
  164. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +0 -0
  165. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  166. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
  167. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  168. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
  169. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  170. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +0 -0
  171. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
  172. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +0 -0
  173. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
  174. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  175. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +0 -0
  176. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
  177. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  178. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +0 -0
  179. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +0 -0
  180. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +0 -0
  181. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
  182. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +0 -0
  183. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +0 -0
  184. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  185. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
  186. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  187. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +0 -0
  188. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/py.typed +0 -0
  189. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/ssh/__init__.py +0 -0
  190. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/ssh/_fabric.py +0 -0
  191. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/string_tools.py +0 -0
  192. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/syringe.py +0 -0
  193. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/__init__.py +0 -0
  194. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/utils.py +0 -0
  195. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/local/__init__.py +0 -0
  196. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/local/_utils.py +0 -0
  197. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/local/collect.py +0 -0
  198. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/local/deactivate.py +0 -0
  199. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/local/reactivate.py +0 -0
  200. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/ssh/__init__.py +0 -0
  201. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/ssh/_utils.py +0 -0
  202. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/ssh/collect.py +0 -0
  203. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/ssh/deactivate.py +0 -0
  204. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/ssh/reactivate.py +0 -0
  205. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/templates/1_create_venv.sh +0 -0
  206. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/templates/2_pip_install.sh +0 -0
  207. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/templates/3_pip_freeze.sh +0 -0
  208. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/templates/4_pip_show.sh +0 -0
  209. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +0 -0
  210. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +0 -0
  211. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/utils_background.py +0 -0
  212. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/utils_package_names.py +0 -0
  213. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/utils_python_interpreter.py +0 -0
  214. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/tasks/v2/utils_templates.py +0 -0
  215. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/urls.py +0 -0
  216. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/utils.py +0 -0
  217. {fractal_server-2.14.0a6 → fractal_server-2.14.0a8}/fractal_server/zip_tools.py +0 -0
@@ -1,8 +1,7 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fractal-server
3
- Version: 2.14.0a6
3
+ Version: 2.14.0a8
4
4
  Summary: Backend component of the Fractal analytics platform
5
- Home-page: https://github.com/fractal-analytics-platform/fractal-server
6
5
  License: BSD-3-Clause
7
6
  Author: Tommaso Comparin
8
7
  Author-email: tommaso.comparin@exact-lab.it
@@ -29,6 +28,7 @@ Requires-Dist: sqlmodel (==0.0.22)
29
28
  Requires-Dist: uvicorn (>=0.29.0,<0.35.0)
30
29
  Requires-Dist: uvicorn-worker (==0.3.0)
31
30
  Project-URL: Documentation, https://fractal-analytics-platform.github.io/fractal-server
31
+ Project-URL: Homepage, https://github.com/fractal-analytics-platform/fractal-server
32
32
  Project-URL: Repository, https://github.com/fractal-analytics-platform/fractal-server
33
33
  Project-URL: changelog, https://github.com/fractal-analytics-platform/fractal-server/blob/main/CHANGELOG.md
34
34
  Description-Content-Type: text/markdown
@@ -0,0 +1 @@
1
+ __VERSION__ = "2.14.0a8"
@@ -419,77 +419,67 @@ async def clean_app_job_list_v2(
419
419
  return submitted_job_ids
420
420
 
421
421
 
422
- async def _get_workflow_check_history_owner(
422
+ async def _get_dataset_or_404(
423
423
  *,
424
- workflow_id: int,
425
424
  dataset_id: int,
426
- user_id: int,
427
425
  db: AsyncSession,
428
- ) -> list[int]:
426
+ ) -> DatasetV2:
429
427
  """
430
- Verify user access for the history of this dataset and workflowtask.
428
+ Get a dataset or raise 404.
431
429
 
432
430
  Args:
433
431
  dataset_id:
434
- workflow_task_id:
435
- user_id:
436
432
  db:
437
-
438
- Returns:
439
- List of WorkflowTask IDs
440
433
  """
441
- workflow = await db.get(WorkflowV2, workflow_id)
442
- if workflow is None:
434
+ ds = await db.get(DatasetV2, dataset_id)
435
+ if ds is None:
443
436
  raise HTTPException(
444
437
  status_code=status.HTTP_404_NOT_FOUND,
445
- detail="Workflow not found.",
438
+ detail=f"Dataset {dataset_id} not found.",
446
439
  )
447
- await _get_project_check_owner(
448
- project_id=workflow.project_id,
449
- user_id=user_id,
450
- db=db,
451
- )
452
- dataset = await db.get(DatasetV2, dataset_id)
453
- if dataset is None:
440
+ else:
441
+ return ds
442
+
443
+
444
+ async def _get_workflow_or_404(
445
+ *,
446
+ workflow_id: int,
447
+ db: AsyncSession,
448
+ ) -> WorkflowV2:
449
+ """
450
+ Get a workflow or raise 404.
451
+
452
+ Args:
453
+ workflow_id:
454
+ db:
455
+ """
456
+ wf = await db.get(WorkflowV2, workflow_id)
457
+ if wf is None:
454
458
  raise HTTPException(
455
459
  status_code=status.HTTP_404_NOT_FOUND,
456
- detail="Dataset not found.",
460
+ detail=f"Workflow {workflow_id} not found.",
457
461
  )
458
- if workflow.project_id != dataset.project_id:
459
- raise HTTPException(
460
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
461
- detail="Dataset and workflow belong to different projects.",
462
- )
463
-
464
- return [wftask.id for wftask in workflow.task_list]
462
+ else:
463
+ return wf
465
464
 
466
465
 
467
- async def _get_workflowtask_check_history_owner(
466
+ async def _get_workflowtask_or_404(
468
467
  *,
469
468
  workflowtask_id: int,
470
- dataset_id: int,
471
- user_id: int,
472
469
  db: AsyncSession,
473
470
  ) -> WorkflowTaskV2:
474
471
  """
475
- Verify user access for the history of this dataset and workflowtask.
472
+ Get a workflow task or raise 404.
476
473
 
477
474
  Args:
478
- dataset_id:
479
- workflow_task_id:
480
- user_id:
475
+ workflowtask_id:
481
476
  db:
482
477
  """
483
- workflowtask = await db.get(WorkflowTaskV2, workflowtask_id)
484
- if workflowtask is None:
478
+ wftask = await db.get(WorkflowTaskV2, workflowtask_id)
479
+ if wftask is None:
485
480
  raise HTTPException(
486
481
  status_code=status.HTTP_404_NOT_FOUND,
487
- detail="WorkflowTask not found.",
482
+ detail=f"WorkflowTask {workflowtask_id} not found.",
488
483
  )
489
- await _get_workflow_check_history_owner(
490
- workflow_id=workflowtask.workflow_id,
491
- dataset_id=dataset_id,
492
- user_id=user_id,
493
- db=db,
494
- )
495
- return workflowtask
484
+ else:
485
+ return wftask
@@ -0,0 +1,158 @@
1
+ from pathlib import Path
2
+ from typing import Literal
3
+
4
+ from fastapi import HTTPException
5
+ from fastapi import status
6
+
7
+ from fractal_server.app.db import AsyncSession
8
+ from fractal_server.app.models import WorkflowTaskV2
9
+ from fractal_server.app.models.v2 import DatasetV2
10
+ from fractal_server.app.models.v2 import HistoryRun
11
+ from fractal_server.app.models.v2 import HistoryUnit
12
+ from fractal_server.app.models.v2 import WorkflowV2
13
+ from fractal_server.app.routes.api.v2._aux_functions import _get_dataset_or_404
14
+ from fractal_server.app.routes.api.v2._aux_functions import (
15
+ _get_project_check_owner,
16
+ )
17
+ from fractal_server.app.routes.api.v2._aux_functions import (
18
+ _get_workflow_or_404,
19
+ )
20
+ from fractal_server.app.routes.api.v2._aux_functions import (
21
+ _get_workflowtask_or_404,
22
+ )
23
+
24
+
25
+ async def get_history_unit_or_404(
26
+ *, history_unit_id: int, db: AsyncSession
27
+ ) -> HistoryUnit:
28
+ """
29
+ Get an existing HistoryUnit or raise a 404.
30
+
31
+ Arguments:
32
+ history_unit_id: The `HistoryUnit` id
33
+ db: An asynchronous db session
34
+ """
35
+ history_unit = await db.get(HistoryUnit, history_unit_id)
36
+ if history_unit is None:
37
+ raise HTTPException(
38
+ status_code=status.HTTP_404_NOT_FOUND,
39
+ detail=f"HistoryUnit {history_unit_id} not found",
40
+ )
41
+ return history_unit
42
+
43
+
44
+ async def get_history_run_or_404(
45
+ *, history_run_id: int, db: AsyncSession
46
+ ) -> HistoryRun:
47
+ """
48
+ Get an existing HistoryRun or raise a 404.
49
+
50
+ Arguments:
51
+ history_run_id:
52
+ db:
53
+ """
54
+ history_run = await db.get(HistoryRun, history_run_id)
55
+ if history_run is None:
56
+ raise HTTPException(
57
+ status_code=status.HTTP_404_NOT_FOUND,
58
+ detail=f"HistoryRun {history_run_id} not found",
59
+ )
60
+ return history_run
61
+
62
+
63
+ def read_log_file(
64
+ *,
65
+ logfile: str | None,
66
+ wftask: WorkflowTaskV2,
67
+ dataset_id: int,
68
+ ):
69
+ if logfile is None or not Path(logfile).exists():
70
+ return (
71
+ f"Logs for task '{wftask.task.name}' in dataset "
72
+ f"{dataset_id} are not available."
73
+ )
74
+
75
+ try:
76
+ with open(logfile, "r") as f:
77
+ return f.read()
78
+ except Exception as e:
79
+ return (
80
+ f"Error while retrieving logs for task '{wftask.task.name}' "
81
+ f"in dataset {dataset_id}. Original error: {str(e)}."
82
+ )
83
+
84
+
85
+ async def _verify_workflow_and_dataset_access(
86
+ *,
87
+ project_id: int,
88
+ workflow_id: int,
89
+ dataset_id: int,
90
+ user_id: int,
91
+ db: AsyncSession,
92
+ ) -> dict[Literal["dataset", "workflow"], DatasetV2 | WorkflowV2]:
93
+ """
94
+ Verify user access to a dataset/workflow pair.
95
+
96
+ Args:
97
+ dataset_id:
98
+ workflow_task_id:
99
+ user_id:
100
+ db:
101
+ """
102
+ await _get_project_check_owner(
103
+ project_id=project_id,
104
+ user_id=user_id,
105
+ db=db,
106
+ )
107
+ workflow = await _get_workflow_or_404(
108
+ workflow_id=workflow_id,
109
+ db=db,
110
+ )
111
+ if workflow.project_id != project_id:
112
+ raise HTTPException(
113
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
114
+ detail="Workflow does not belong to expected project.",
115
+ )
116
+ dataset = await _get_dataset_or_404(
117
+ dataset_id=dataset_id,
118
+ db=db,
119
+ )
120
+ if dataset.project_id != project_id:
121
+ raise HTTPException(
122
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
123
+ detail="Dataset does not belong to expected project.",
124
+ )
125
+
126
+ return dict(dataset=dataset, workflow=workflow)
127
+
128
+
129
+ async def get_wftask_check_owner(
130
+ *,
131
+ project_id: int,
132
+ dataset_id: int,
133
+ workflowtask_id: int,
134
+ user_id: int,
135
+ db: AsyncSession,
136
+ ) -> WorkflowTaskV2:
137
+ """
138
+ Verify user access for the history of this dataset and workflowtask.
139
+
140
+ Args:
141
+ project_id:
142
+ dataset_id:
143
+ workflow_task_id:
144
+ user_id:
145
+ db:
146
+ """
147
+ wftask = await _get_workflowtask_or_404(
148
+ workflowtask_id=workflowtask_id,
149
+ db=db,
150
+ )
151
+ await _verify_workflow_and_dataset_access(
152
+ project_id=project_id,
153
+ dataset_id=dataset_id,
154
+ workflow_id=wftask.workflow_id,
155
+ user_id=user_id,
156
+ db=db,
157
+ )
158
+ return wftask
@@ -1,26 +1,20 @@
1
- from datetime import datetime
2
- from typing import Any
3
- from typing import Optional
4
-
5
1
  from fastapi import APIRouter
6
2
  from fastapi import Depends
7
3
  from fastapi import HTTPException
8
4
  from fastapi import status
9
5
  from fastapi.responses import JSONResponse
10
- from pydantic import AwareDatetime
11
- from pydantic import BaseModel
12
- from pydantic import field_serializer
13
6
  from sqlmodel import func
14
7
  from sqlmodel import select
15
8
 
16
9
  from ._aux_functions import _get_dataset_check_owner
17
10
  from ._aux_functions import _get_workflow_check_owner
18
- from ._aux_functions import _get_workflowtask_check_history_owner
11
+ from ._aux_functions_history import _verify_workflow_and_dataset_access
12
+ from ._aux_functions_history import get_history_run_or_404
19
13
  from ._aux_functions_history import get_history_unit_or_404
14
+ from ._aux_functions_history import get_wftask_check_owner
20
15
  from ._aux_functions_history import read_log_file
21
16
  from fractal_server.app.db import AsyncSession
22
17
  from fractal_server.app.db import get_async_db
23
- from fractal_server.app.history.status_enum import XXXStatus
24
18
  from fractal_server.app.models import UserOAuth
25
19
  from fractal_server.app.models.v2 import HistoryImageCache
26
20
  from fractal_server.app.models.v2 import HistoryRun
@@ -29,6 +23,11 @@ from fractal_server.app.routes.auth import current_active_user
29
23
  from fractal_server.app.routes.pagination import get_pagination_params
30
24
  from fractal_server.app.routes.pagination import PaginationRequest
31
25
  from fractal_server.app.routes.pagination import PaginationResponse
26
+ from fractal_server.app.schemas.v2 import HistoryRunReadAggregated
27
+ from fractal_server.app.schemas.v2 import HistoryUnitRead
28
+ from fractal_server.app.schemas.v2 import HistoryUnitStatus
29
+ from fractal_server.app.schemas.v2 import ImageLogsRequest
30
+ from fractal_server.app.schemas.v2 import ZarrUrlAndStatus
32
31
  from fractal_server.images.tools import filter_image_list
33
32
  from fractal_server.images.tools import merge_type_filters
34
33
  from fractal_server.logger import set_logger
@@ -45,12 +44,21 @@ async def get_workflow_tasks_statuses(
45
44
  user: UserOAuth = Depends(current_active_user),
46
45
  db: AsyncSession = Depends(get_async_db),
47
46
  ) -> JSONResponse:
47
+
48
+ # Access control
48
49
  workflow = await _get_workflow_check_owner(
49
50
  project_id=project_id,
50
51
  workflow_id=workflow_id,
51
52
  user_id=user.id,
52
53
  db=db,
53
54
  )
55
+ await _get_dataset_check_owner(
56
+ project_id=project_id,
57
+ dataset_id=dataset_id,
58
+ user_id=user.id,
59
+ db=db,
60
+ )
61
+
54
62
  response = {}
55
63
  for wftask in workflow.task_list:
56
64
  res = await db.execute(
@@ -69,7 +77,7 @@ async def get_workflow_tasks_statuses(
69
77
  num_available_images=latest_history_run.num_available_images,
70
78
  )
71
79
 
72
- for target_status in XXXStatus:
80
+ for target_status in HistoryUnitStatus:
73
81
  stm = (
74
82
  select(func.count(HistoryImageCache.zarr_url))
75
83
  .join(HistoryUnit)
@@ -89,43 +97,6 @@ async def get_workflow_tasks_statuses(
89
97
  return JSONResponse(content=response, status_code=200)
90
98
 
91
99
 
92
- # FIXME MOVE TO SCHEMAS
93
-
94
-
95
- class HistoryUnitRead(BaseModel):
96
- id: int
97
- logfile: Optional[str] = None
98
- status: XXXStatus
99
- zarr_urls: list[str]
100
-
101
-
102
- class HistoryRunReadAggregated(BaseModel):
103
- id: int
104
- timestamp_started: AwareDatetime
105
- workflowtask_dump: dict[str, Any]
106
- num_submitted_units: int
107
- num_done_units: int
108
- num_failed_units: int
109
-
110
- @field_serializer("timestamp_started")
111
- def serialize_datetime(v: datetime) -> str:
112
- return v.isoformat()
113
-
114
-
115
- class ImageLogsRequest(BaseModel):
116
- workflowtask_id: int
117
- dataset_id: int
118
- zarr_url: str
119
-
120
-
121
- class ImageWithStatus(BaseModel):
122
- zarr_url: str
123
- status: Optional[XXXStatus] = None
124
-
125
-
126
- # end FIXME
127
-
128
-
129
100
  @router.get("/project/{project_id}/status/run/")
130
101
  async def get_history_run_list(
131
102
  project_id: int,
@@ -134,8 +105,10 @@ async def get_history_run_list(
134
105
  user: UserOAuth = Depends(current_active_user),
135
106
  db: AsyncSession = Depends(get_async_db),
136
107
  ) -> list[HistoryRunReadAggregated]:
108
+
137
109
  # Access control
138
- await _get_workflowtask_check_history_owner(
110
+ await get_wftask_check_owner(
111
+ project_id=project_id,
139
112
  dataset_id=dataset_id,
140
113
  workflowtask_id=workflowtask_id,
141
114
  user_id=user.id,
@@ -152,11 +125,11 @@ async def get_history_run_list(
152
125
  res = await db.execute(stm)
153
126
  runs = res.scalars().all()
154
127
 
155
- # Add units count by status
156
-
128
+ # Respond early if there are no runs
157
129
  if not runs:
158
130
  return []
159
131
 
132
+ # Add units count by status
160
133
  run_ids = [run.id for run in runs]
161
134
  stm = (
162
135
  select(
@@ -196,30 +169,29 @@ async def get_history_run_units(
196
169
  db: AsyncSession = Depends(get_async_db),
197
170
  pagination: PaginationRequest = Depends(get_pagination_params),
198
171
  ) -> PaginationResponse[HistoryUnitRead]:
172
+
199
173
  # Access control
200
- await _get_workflowtask_check_history_owner(
174
+ await get_wftask_check_owner(
175
+ project_id=project_id,
201
176
  dataset_id=dataset_id,
202
177
  workflowtask_id=workflowtask_id,
203
178
  user_id=user.id,
204
179
  db=db,
205
180
  )
206
181
 
207
- history_run = await db.get(HistoryRun, history_run_id)
208
- if history_run is None:
209
- raise HTTPException(
210
- status_code=status.HTTP_404_NOT_FOUND,
211
- detail=f"HistoryRun {history_run_id} not found",
212
- )
182
+ # Check that `HistoryRun` exists
183
+ await get_history_run_or_404(history_run_id=history_run_id, db=db)
213
184
 
185
+ # Count `HistoryUnit`s
214
186
  res = await db.execute(
215
187
  select(func.count(HistoryUnit.id)).where(
216
188
  HistoryUnit.history_run_id == history_run_id
217
189
  )
218
190
  )
219
191
  total_count = res.scalar()
220
-
221
192
  page_size = pagination.page_size or total_count
222
193
 
194
+ # Query `HistoryUnit`s
223
195
  res = await db.execute(
224
196
  select(HistoryUnit)
225
197
  .where(HistoryUnit.history_run_id == history_run_id)
@@ -244,30 +216,27 @@ async def get_history_images(
244
216
  user: UserOAuth = Depends(current_active_user),
245
217
  db: AsyncSession = Depends(get_async_db),
246
218
  pagination: PaginationRequest = Depends(get_pagination_params),
247
- ) -> PaginationResponse[ImageWithStatus]:
219
+ ) -> PaginationResponse[ZarrUrlAndStatus]:
220
+
248
221
  # Access control and object retrieval
249
- # FIXME: Provide a single function that checks/gets what is needed
250
- res = await _get_dataset_check_owner(
222
+ wftask = await get_wftask_check_owner(
251
223
  project_id=project_id,
252
- dataset_id=dataset_id,
253
- user_id=user.id,
254
- db=db,
255
- )
256
- dataset = res["dataset"]
257
- wftask = await _get_workflowtask_check_history_owner(
258
224
  dataset_id=dataset_id,
259
225
  workflowtask_id=workflowtask_id,
260
226
  user_id=user.id,
261
227
  db=db,
262
228
  )
263
- workflow = await _get_workflow_check_owner(
229
+ res = await _verify_workflow_and_dataset_access(
264
230
  project_id=project_id,
265
231
  workflow_id=wftask.workflow_id,
232
+ dataset_id=dataset_id,
266
233
  user_id=user.id,
267
234
  db=db,
268
235
  )
236
+ dataset = res["dataset"]
237
+ workflow = res["workflow"]
269
238
 
270
- # FIXME reduce logging?
239
+ # Setup prefix for logging
271
240
  prefix = f"[DS{dataset.id}-WFT{wftask.id}-images]"
272
241
 
273
242
  # (1) Get the type-filtered list of dataset images
@@ -359,7 +328,8 @@ async def get_image_log(
359
328
  db: AsyncSession = Depends(get_async_db),
360
329
  ) -> JSONResponse:
361
330
  # Access control
362
- wftask = await _get_workflowtask_check_history_owner(
331
+ wftask = await get_wftask_check_owner(
332
+ project_id=project_id,
363
333
  dataset_id=request_data.dataset_id,
364
334
  workflowtask_id=request_data.workflowtask_id,
365
335
  user_id=user.id,
@@ -406,7 +376,8 @@ async def get_history_unit_log(
406
376
  db: AsyncSession = Depends(get_async_db),
407
377
  ) -> JSONResponse:
408
378
  # Access control
409
- wftask = await _get_workflowtask_check_history_owner(
379
+ wftask = await get_wftask_check_owner(
380
+ project_id=project_id,
410
381
  dataset_id=dataset_id,
411
382
  workflowtask_id=workflowtask_id,
412
383
  user_id=user.id,
@@ -5,6 +5,7 @@ from typing import Optional
5
5
 
6
6
  from fastapi import APIRouter
7
7
  from fastapi import Depends
8
+ from fastapi import HTTPException
8
9
  from fastapi import Response
9
10
  from fastapi import status
10
11
  from fastapi.responses import StreamingResponse
@@ -83,6 +84,35 @@ async def get_workflow_jobs(
83
84
  return job_list
84
85
 
85
86
 
87
+ @router.get("/project/{project_id}/latest-job/")
88
+ async def get_latest_job(
89
+ project_id: int,
90
+ workflow_id: int,
91
+ dataset_id: int,
92
+ user: UserOAuth = Depends(current_active_user),
93
+ db: AsyncSession = Depends(get_async_db),
94
+ ) -> JobReadV2:
95
+ await _get_workflow_check_owner(
96
+ project_id=project_id, workflow_id=workflow_id, user_id=user.id, db=db
97
+ )
98
+ stm = (
99
+ select(JobV2)
100
+ .where(JobV2.project_id == project_id)
101
+ .where(JobV2.workflow_id == workflow_id)
102
+ .where(JobV2.dataset_id == dataset_id)
103
+ .order_by(JobV2.start_timestamp.desc())
104
+ .limit(1)
105
+ )
106
+ res = await db.execute(stm)
107
+ latest_job = res.scalar_one_or_none()
108
+ if latest_job is None:
109
+ raise HTTPException(
110
+ status_code=status.HTTP_404_NOT_FOUND,
111
+ detail=f"Job with {workflow_id=} and {dataset_id=} not found.",
112
+ )
113
+ return latest_job
114
+
115
+
86
116
  @router.get(
87
117
  "/project/{project_id}/job/{job_id}/",
88
118
  response_model=JobReadV2,
@@ -9,8 +9,8 @@ from .....logger import set_logger
9
9
  from ....db import AsyncSession
10
10
  from ....db import get_async_db
11
11
  from ....models.v2 import JobV2
12
- from ....schemas.v2.status import StatusReadV2
13
- from ....schemas.v2.workflowtask import WorkflowTaskStatusTypeV2
12
+ from ....schemas.v2.status_legacy import LegacyStatusReadV2
13
+ from ....schemas.v2.status_legacy import WorkflowTaskStatusTypeV2
14
14
  from ._aux_functions import _get_dataset_check_owner
15
15
  from ._aux_functions import _get_submitted_jobs_statement
16
16
  from ._aux_functions import _get_workflow_check_owner
@@ -24,7 +24,7 @@ logger = set_logger(__name__)
24
24
 
25
25
  @router.get(
26
26
  "/project/{project_id}/status-legacy/",
27
- response_model=StatusReadV2,
27
+ response_model=LegacyStatusReadV2,
28
28
  )
29
29
  async def get_workflowtask_status(
30
30
  project_id: int,
@@ -32,7 +32,7 @@ async def get_workflowtask_status(
32
32
  workflow_id: int,
33
33
  user: UserOAuth = Depends(current_active_user),
34
34
  db: AsyncSession = Depends(get_async_db),
35
- ) -> Optional[StatusReadV2]:
35
+ ) -> Optional[LegacyStatusReadV2]:
36
36
  """
37
37
  Extract the status of all `WorkflowTaskV2` of a given `WorkflowV2` that ran
38
38
  on a given `DatasetV2`.
@@ -164,5 +164,5 @@ async def get_workflowtask_status(
164
164
  # first time that you hit `last_valid_wftask_id``
165
165
  break
166
166
 
167
- response_body = StatusReadV2(status=clean_workflow_tasks_status_dict)
167
+ response_body = LegacyStatusReadV2(status=clean_workflow_tasks_status_dict)
168
168
  return response_body
@@ -152,14 +152,7 @@ async def create_task(
152
152
  db=db,
153
153
  )
154
154
 
155
- if task.command_non_parallel is None:
156
- task_type = "parallel"
157
- elif task.command_parallel is None:
158
- task_type = "non_parallel"
159
- else:
160
- task_type = "compound"
161
-
162
- if task_type == "parallel" and (
155
+ if task.type == "parallel" and (
163
156
  task.args_schema_non_parallel is not None
164
157
  or task.meta_non_parallel is not None
165
158
  ):
@@ -170,7 +163,7 @@ async def create_task(
170
163
  "`TaskV2.args_schema_non_parallel` if TaskV2 is parallel"
171
164
  ),
172
165
  )
173
- elif task_type == "non_parallel" and (
166
+ elif task.type == "non_parallel" and (
174
167
  task.args_schema_parallel is not None or task.meta_parallel is not None
175
168
  ):
176
169
  raise HTTPException(
@@ -183,7 +176,7 @@ async def create_task(
183
176
 
184
177
  # Add task
185
178
 
186
- db_task = TaskV2(**task.model_dump(exclude_unset=True), type=task_type)
179
+ db_task = TaskV2(**task.model_dump(exclude_unset=True))
187
180
  pkg_name = db_task.name
188
181
  await _verify_non_duplication_user_constraint(
189
182
  db=db, pkg_name=pkg_name, user_id=user.id, version=db_task.version
@@ -56,7 +56,14 @@ async def replace_workflowtask(
56
56
  )
57
57
 
58
58
  # Preliminary checks
59
- if old_wftask.task_type != new_task.type:
59
+ EQUIVALENT_TASK_TYPES = [
60
+ {"non_parallel", "converter_non_parallel"},
61
+ {"compound", "converter_compound"},
62
+ ]
63
+ if (
64
+ old_wftask.task_type != new_task.type
65
+ and {old_wftask.task_type, new_task.type} not in EQUIVALENT_TASK_TYPES
66
+ ):
60
67
  raise HTTPException(
61
68
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
62
69
  detail=(
@@ -64,6 +71,7 @@ async def replace_workflowtask(
64
71
  f"{old_wftask.task_type} to {new_task.type}."
65
72
  ),
66
73
  )
74
+
67
75
  if replace.args_non_parallel is not None and new_task.type == "parallel":
68
76
  raise HTTPException(
69
77
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
@@ -259,7 +267,10 @@ async def update_workflowtask(
259
267
  "parallel."
260
268
  ),
261
269
  )
262
- elif db_wf_task.task_type == "non_parallel" and (
270
+ elif db_wf_task.task_type in [
271
+ "non_parallel",
272
+ "converter_non_parallel",
273
+ ] and (
263
274
  workflow_task_update.args_parallel is not None
264
275
  or workflow_task_update.meta_parallel is not None
265
276
  ):