fractal-server 2.17.1a1__py3-none-any.whl → 2.18.0__py3-none-any.whl

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 (225) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +21 -19
  3. fractal_server/app/db/__init__.py +3 -3
  4. fractal_server/app/models/__init__.py +1 -0
  5. fractal_server/app/models/linkuserproject.py +43 -1
  6. fractal_server/app/models/security.py +28 -8
  7. fractal_server/app/models/v2/__init__.py +3 -1
  8. fractal_server/app/models/v2/accounting.py +9 -1
  9. fractal_server/app/models/v2/dataset.py +5 -1
  10. fractal_server/app/models/v2/history.py +15 -1
  11. fractal_server/app/models/v2/job.py +17 -2
  12. fractal_server/app/models/v2/profile.py +29 -0
  13. fractal_server/app/models/v2/project.py +4 -10
  14. fractal_server/app/models/v2/resource.py +17 -0
  15. fractal_server/app/models/v2/task_group.py +4 -3
  16. fractal_server/app/models/v2/workflow.py +2 -1
  17. fractal_server/app/routes/admin/v2/__init__.py +12 -13
  18. fractal_server/app/routes/admin/v2/accounting.py +3 -3
  19. fractal_server/app/routes/admin/v2/job.py +35 -24
  20. fractal_server/app/routes/admin/v2/profile.py +3 -2
  21. fractal_server/app/routes/admin/v2/resource.py +5 -5
  22. fractal_server/app/routes/admin/v2/sharing.py +103 -0
  23. fractal_server/app/routes/admin/v2/task.py +37 -26
  24. fractal_server/app/routes/admin/v2/task_group.py +94 -17
  25. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +21 -22
  26. fractal_server/app/routes/api/__init__.py +1 -9
  27. fractal_server/app/routes/api/v2/__init__.py +49 -50
  28. fractal_server/app/routes/api/v2/_aux_functions.py +132 -124
  29. fractal_server/app/routes/api/v2/_aux_functions_history.py +51 -23
  30. fractal_server/app/routes/api/v2/_aux_functions_sharing.py +97 -0
  31. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +6 -8
  32. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +7 -9
  33. fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +1 -2
  34. fractal_server/app/routes/api/v2/dataset.py +95 -102
  35. fractal_server/app/routes/api/v2/history.py +59 -33
  36. fractal_server/app/routes/api/v2/images.py +24 -9
  37. fractal_server/app/routes/api/v2/job.py +52 -33
  38. fractal_server/app/routes/api/v2/pre_submission_checks.py +16 -8
  39. fractal_server/app/routes/api/v2/project.py +65 -37
  40. fractal_server/app/routes/api/v2/sharing.py +311 -0
  41. fractal_server/app/routes/api/v2/status_legacy.py +31 -41
  42. fractal_server/app/routes/api/v2/submit.py +82 -78
  43. fractal_server/app/routes/api/v2/task.py +19 -20
  44. fractal_server/app/routes/api/v2/task_collection.py +41 -43
  45. fractal_server/app/routes/api/v2/task_collection_custom.py +19 -20
  46. fractal_server/app/routes/api/v2/task_collection_pixi.py +10 -11
  47. fractal_server/app/routes/api/v2/task_group.py +25 -24
  48. fractal_server/app/routes/api/v2/task_group_lifecycle.py +32 -32
  49. fractal_server/app/routes/api/v2/task_version_update.py +23 -19
  50. fractal_server/app/routes/api/v2/workflow.py +50 -55
  51. fractal_server/app/routes/api/v2/workflow_import.py +37 -37
  52. fractal_server/app/routes/api/v2/workflowtask.py +32 -26
  53. fractal_server/app/routes/auth/__init__.py +1 -3
  54. fractal_server/app/routes/auth/_aux_auth.py +101 -2
  55. fractal_server/app/routes/auth/current_user.py +2 -66
  56. fractal_server/app/routes/auth/group.py +8 -35
  57. fractal_server/app/routes/auth/login.py +1 -0
  58. fractal_server/app/routes/auth/oauth.py +4 -3
  59. fractal_server/app/routes/auth/register.py +4 -2
  60. fractal_server/app/routes/auth/router.py +2 -0
  61. fractal_server/app/routes/auth/users.py +19 -10
  62. fractal_server/app/routes/auth/viewer_paths.py +43 -0
  63. fractal_server/app/routes/aux/_job.py +1 -1
  64. fractal_server/app/routes/aux/_runner.py +2 -2
  65. fractal_server/app/routes/pagination.py +1 -1
  66. fractal_server/app/schemas/user.py +29 -12
  67. fractal_server/app/schemas/user_group.py +0 -15
  68. fractal_server/app/schemas/v2/__init__.py +55 -48
  69. fractal_server/app/schemas/v2/accounting.py +11 -0
  70. fractal_server/app/schemas/v2/dataset.py +57 -11
  71. fractal_server/app/schemas/v2/dumps.py +10 -9
  72. fractal_server/app/schemas/v2/job.py +11 -11
  73. fractal_server/app/schemas/v2/manifest.py +4 -3
  74. fractal_server/app/schemas/v2/profile.py +53 -2
  75. fractal_server/app/schemas/v2/project.py +3 -3
  76. fractal_server/app/schemas/v2/resource.py +121 -16
  77. fractal_server/app/schemas/v2/sharing.py +99 -0
  78. fractal_server/app/schemas/v2/status_legacy.py +3 -3
  79. fractal_server/app/schemas/v2/task.py +6 -7
  80. fractal_server/app/schemas/v2/task_collection.py +5 -5
  81. fractal_server/app/schemas/v2/task_group.py +16 -16
  82. fractal_server/app/schemas/v2/workflow.py +16 -16
  83. fractal_server/app/schemas/v2/workflowtask.py +16 -15
  84. fractal_server/app/security/__init__.py +5 -8
  85. fractal_server/app/security/signup_email.py +4 -5
  86. fractal_server/app/shutdown.py +6 -6
  87. fractal_server/config/__init__.py +0 -6
  88. fractal_server/config/_data.py +0 -68
  89. fractal_server/config/_database.py +19 -20
  90. fractal_server/config/_email.py +30 -38
  91. fractal_server/config/_main.py +38 -52
  92. fractal_server/config/_oauth.py +17 -21
  93. fractal_server/data_migrations/2_18_0.py +30 -0
  94. fractal_server/exceptions.py +4 -0
  95. fractal_server/images/models.py +4 -5
  96. fractal_server/images/status_tools.py +4 -2
  97. fractal_server/logger.py +1 -1
  98. fractal_server/main.py +75 -13
  99. fractal_server/migrations/versions/034a469ec2eb_task_groups.py +4 -8
  100. fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +1 -1
  101. fractal_server/migrations/versions/0f5f85bb2ae7_add_pre_pinned_packages.py +1 -0
  102. fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +1 -1
  103. fractal_server/migrations/versions/1a83a5260664_rename.py +1 -1
  104. fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +1 -0
  105. fractal_server/migrations/versions/316140ff7ee1_remove_usersettings_cache_dir.py +1 -1
  106. fractal_server/migrations/versions/40d6d6511b20_add_index_to_history_models.py +47 -0
  107. fractal_server/migrations/versions/45fbb391d7af_make_resource_id_fk_non_nullable.py +1 -1
  108. fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +1 -0
  109. fractal_server/migrations/versions/49d0856e9569_drop_table.py +2 -3
  110. fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +1 -1
  111. fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +1 -1
  112. fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +2 -1
  113. fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +7 -19
  114. fractal_server/migrations/versions/5bf02391cfef_v2.py +4 -10
  115. fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +1 -0
  116. fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +1 -1
  117. fractal_server/migrations/versions/7673fe18c05d_remove_project_dir_server_default.py +1 -1
  118. fractal_server/migrations/versions/7910eed4cf97_user_project_dirs_and_usergroup_viewer_.py +60 -0
  119. fractal_server/migrations/versions/791ce783d3d8_add_indices.py +1 -1
  120. fractal_server/migrations/versions/83bc2ad3ffcc_2_17_0.py +1 -0
  121. fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +1 -0
  122. fractal_server/migrations/versions/88270f589c9b_add_prevent_new_submissions.py +39 -0
  123. fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +2 -4
  124. fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +1 -1
  125. fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +1 -0
  126. fractal_server/migrations/versions/969d84257cac_add_historyrun_task_id.py +1 -1
  127. fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +1 -1
  128. fractal_server/migrations/versions/981d588fe248_add_executor_error_log.py +1 -1
  129. fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +2 -4
  130. fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +1 -1
  131. fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +1 -1
  132. fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +1 -1
  133. fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +1 -1
  134. fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +1 -0
  135. fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +1 -0
  136. fractal_server/migrations/versions/b1e7f7a1ff71_task_group_for_pixi.py +1 -1
  137. fractal_server/migrations/versions/b3ffb095f973_json_to_jsonb.py +1 -0
  138. fractal_server/migrations/versions/bc0e8b3327a7_project_sharing.py +72 -0
  139. fractal_server/migrations/versions/c90a7c76e996_job_id_in_history_run.py +1 -1
  140. fractal_server/migrations/versions/caba9fb1ea5e_drop_useroauth_user_settings_id.py +1 -1
  141. fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +4 -9
  142. fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +1 -0
  143. fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +1 -1
  144. fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +1 -0
  145. fractal_server/migrations/versions/e0e717ae2f26_delete_linkuserproject_ondelete_project.py +50 -0
  146. fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +1 -0
  147. fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +1 -1
  148. fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +1 -0
  149. fractal_server/migrations/versions/f0702066b007_one_submitted_job_per_dataset.py +40 -0
  150. fractal_server/migrations/versions/f37aceb45062_make_historyunit_logfile_required.py +1 -1
  151. fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +1 -0
  152. fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +4 -9
  153. fractal_server/runner/config/_local.py +8 -5
  154. fractal_server/runner/config/_slurm.py +39 -33
  155. fractal_server/runner/config/slurm_mem_to_MB.py +0 -1
  156. fractal_server/runner/executors/base_runner.py +29 -4
  157. fractal_server/runner/executors/local/get_local_config.py +1 -0
  158. fractal_server/runner/executors/local/runner.py +14 -13
  159. fractal_server/runner/executors/slurm_common/_batching.py +9 -20
  160. fractal_server/runner/executors/slurm_common/base_slurm_runner.py +53 -27
  161. fractal_server/runner/executors/slurm_common/get_slurm_config.py +14 -7
  162. fractal_server/runner/executors/slurm_common/remote.py +3 -1
  163. fractal_server/runner/executors/slurm_common/slurm_config.py +2 -0
  164. fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -3
  165. fractal_server/runner/executors/slurm_ssh/runner.py +16 -11
  166. fractal_server/runner/executors/slurm_ssh/tar_commands.py +1 -0
  167. fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +1 -0
  168. fractal_server/runner/executors/slurm_sudo/runner.py +16 -11
  169. fractal_server/runner/task_files.py +9 -3
  170. fractal_server/runner/v2/_local.py +12 -6
  171. fractal_server/runner/v2/_slurm_ssh.py +14 -7
  172. fractal_server/runner/v2/_slurm_sudo.py +14 -7
  173. fractal_server/runner/v2/db_tools.py +0 -1
  174. fractal_server/runner/v2/deduplicate_list.py +2 -1
  175. fractal_server/runner/v2/runner.py +44 -28
  176. fractal_server/runner/v2/runner_functions.py +22 -28
  177. fractal_server/runner/v2/submit_workflow.py +29 -15
  178. fractal_server/ssh/_fabric.py +6 -13
  179. fractal_server/string_tools.py +0 -1
  180. fractal_server/syringe.py +1 -1
  181. fractal_server/tasks/config/_pixi.py +1 -1
  182. fractal_server/tasks/config/_python.py +16 -9
  183. fractal_server/tasks/utils.py +0 -1
  184. fractal_server/tasks/v2/local/_utils.py +3 -3
  185. fractal_server/tasks/v2/local/collect.py +15 -18
  186. fractal_server/tasks/v2/local/collect_pixi.py +14 -16
  187. fractal_server/tasks/v2/local/deactivate.py +14 -15
  188. fractal_server/tasks/v2/local/deactivate_pixi.py +7 -7
  189. fractal_server/tasks/v2/local/delete.py +6 -8
  190. fractal_server/tasks/v2/local/reactivate.py +12 -12
  191. fractal_server/tasks/v2/local/reactivate_pixi.py +12 -12
  192. fractal_server/tasks/v2/ssh/_utils.py +3 -3
  193. fractal_server/tasks/v2/ssh/collect.py +19 -24
  194. fractal_server/tasks/v2/ssh/collect_pixi.py +22 -24
  195. fractal_server/tasks/v2/ssh/deactivate.py +17 -15
  196. fractal_server/tasks/v2/ssh/deactivate_pixi.py +8 -7
  197. fractal_server/tasks/v2/ssh/delete.py +12 -10
  198. fractal_server/tasks/v2/ssh/reactivate.py +16 -16
  199. fractal_server/tasks/v2/ssh/reactivate_pixi.py +13 -14
  200. fractal_server/tasks/v2/templates/1_create_venv.sh +2 -0
  201. fractal_server/tasks/v2/templates/2_pip_install.sh +2 -0
  202. fractal_server/tasks/v2/templates/3_pip_freeze.sh +2 -0
  203. fractal_server/tasks/v2/templates/4_pip_show.sh +2 -0
  204. fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +3 -1
  205. fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +2 -0
  206. fractal_server/tasks/v2/templates/pixi_1_extract.sh +2 -0
  207. fractal_server/tasks/v2/templates/pixi_2_install.sh +2 -0
  208. fractal_server/tasks/v2/templates/pixi_3_post_install.sh +2 -0
  209. fractal_server/tasks/v2/utils_background.py +10 -10
  210. fractal_server/tasks/v2/utils_database.py +5 -5
  211. fractal_server/tasks/v2/utils_package_names.py +1 -2
  212. fractal_server/tasks/v2/utils_pixi.py +1 -3
  213. fractal_server/types/__init__.py +98 -1
  214. fractal_server/types/validators/__init__.py +3 -0
  215. fractal_server/types/validators/_common_validators.py +33 -3
  216. fractal_server/types/validators/_workflow_task_arguments_validators.py +1 -2
  217. fractal_server/utils.py +1 -0
  218. fractal_server/zip_tools.py +34 -0
  219. {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/METADATA +3 -2
  220. fractal_server-2.18.0.dist-info/RECORD +275 -0
  221. fractal_server/app/routes/admin/v2/project.py +0 -41
  222. fractal_server-2.17.1a1.dist-info/RECORD +0 -264
  223. {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/WHEEL +0 -0
  224. {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/entry_points.txt +0 -0
  225. {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,40 +6,43 @@ from fastapi.responses import JSONResponse
6
6
  from sqlmodel import func
7
7
  from sqlmodel import select
8
8
 
9
- from ._aux_functions import _get_dataset_check_owner
10
- from ._aux_functions import _get_submitted_job_or_none
11
- from ._aux_functions import _get_workflow_check_owner
12
- from ._aux_functions_history import _verify_workflow_and_dataset_access
13
- from ._aux_functions_history import get_history_run_or_404
14
- from ._aux_functions_history import get_history_unit_or_404
15
- from ._aux_functions_history import get_wftask_check_owner
16
- from ._aux_functions_history import read_log_file
17
- from .images import ImagePage
18
- from .images import ImageQuery
19
9
  from fractal_server.app.db import AsyncSession
20
10
  from fractal_server.app.db import get_async_db
21
11
  from fractal_server.app.models import UserOAuth
22
12
  from fractal_server.app.models.v2 import HistoryImageCache
23
13
  from fractal_server.app.models.v2 import HistoryRun
24
14
  from fractal_server.app.models.v2 import HistoryUnit
15
+ from fractal_server.app.models.v2 import JobV2
25
16
  from fractal_server.app.models.v2 import TaskV2
26
17
  from fractal_server.app.routes.auth import current_user_act_ver_prof
27
- from fractal_server.app.routes.pagination import get_pagination_params
28
18
  from fractal_server.app.routes.pagination import PaginationRequest
29
19
  from fractal_server.app.routes.pagination import PaginationResponse
20
+ from fractal_server.app.routes.pagination import get_pagination_params
30
21
  from fractal_server.app.schemas.v2 import HistoryRunRead
31
22
  from fractal_server.app.schemas.v2 import HistoryRunReadAggregated
32
23
  from fractal_server.app.schemas.v2 import HistoryUnitRead
33
24
  from fractal_server.app.schemas.v2 import HistoryUnitStatus
34
25
  from fractal_server.app.schemas.v2 import HistoryUnitStatusWithUnset
35
26
  from fractal_server.app.schemas.v2 import ImageLogsRequest
36
- from fractal_server.images.status_tools import enrich_images_unsorted_async
27
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
37
28
  from fractal_server.images.status_tools import IMAGE_STATUS_KEY
29
+ from fractal_server.images.status_tools import enrich_images_unsorted_async
38
30
  from fractal_server.images.tools import aggregate_attributes
39
31
  from fractal_server.images.tools import aggregate_types
40
32
  from fractal_server.images.tools import filter_image_list
41
33
  from fractal_server.logger import set_logger
42
34
 
35
+ from ._aux_functions import _get_dataset_check_access
36
+ from ._aux_functions import _get_submitted_jobs_statement
37
+ from ._aux_functions import _get_workflow_check_access
38
+ from ._aux_functions_history import _verify_workflow_and_dataset_access
39
+ from ._aux_functions_history import get_history_run_or_404
40
+ from ._aux_functions_history import get_history_unit_or_404
41
+ from ._aux_functions_history import get_wftask_check_access
42
+ from ._aux_functions_history import read_log_file
43
+ from .images import ImagePage
44
+ from .images import ImageQuery
45
+
43
46
 
44
47
  def check_historyrun_related_to_dataset_and_wftask(
45
48
  history_run: HistoryRun,
@@ -72,24 +75,28 @@ async def get_workflow_tasks_statuses(
72
75
  db: AsyncSession = Depends(get_async_db),
73
76
  ) -> JSONResponse:
74
77
  # Access control
75
- workflow = await _get_workflow_check_owner(
78
+ workflow = await _get_workflow_check_access(
76
79
  project_id=project_id,
77
80
  workflow_id=workflow_id,
78
81
  user_id=user.id,
82
+ required_permissions=ProjectPermissions.READ,
79
83
  db=db,
80
84
  )
81
- await _get_dataset_check_owner(
85
+ await _get_dataset_check_access(
82
86
  project_id=project_id,
83
87
  dataset_id=dataset_id,
84
88
  user_id=user.id,
89
+ required_permissions=ProjectPermissions.READ,
85
90
  db=db,
86
91
  )
87
92
 
88
- running_job = await _get_submitted_job_or_none(
89
- db=db,
90
- dataset_id=dataset_id,
91
- workflow_id=workflow_id,
93
+ res = await db.execute(
94
+ _get_submitted_jobs_statement()
95
+ .where(JobV2.dataset_id == dataset_id)
96
+ .where(JobV2.workflow_id == workflow_id)
92
97
  )
98
+ running_job = res.scalars().one_or_none()
99
+
93
100
  if running_job is not None:
94
101
  running_wftasks = workflow.task_list[
95
102
  running_job.first_task_index : running_job.last_task_index + 1
@@ -135,19 +142,19 @@ async def get_workflow_tasks_statuses(
135
142
  logger.debug(f"C1: {wftask.id=} not in {running_wftask_ids=}.")
136
143
  response[wftask.id] = dict(status=latest_run.status)
137
144
 
138
- response[wftask.id][
139
- "num_available_images"
140
- ] = latest_run.num_available_images
145
+ response[wftask.id]["num_available_images"] = (
146
+ latest_run.num_available_images
147
+ )
141
148
 
142
149
  for target_status in HistoryUnitStatus:
143
150
  stm = (
144
151
  select(func.count(HistoryImageCache.zarr_url))
145
- .join(HistoryUnit)
152
+ .join(
153
+ HistoryUnit,
154
+ HistoryImageCache.latest_history_unit_id == HistoryUnit.id,
155
+ )
146
156
  .where(HistoryImageCache.dataset_id == dataset_id)
147
157
  .where(HistoryImageCache.workflowtask_id == wftask.id)
148
- .where(
149
- HistoryImageCache.latest_history_unit_id == HistoryUnit.id
150
- )
151
158
  .where(HistoryUnit.status == target_status)
152
159
  )
153
160
  res = await db.execute(stm)
@@ -183,11 +190,12 @@ async def get_history_run_list(
183
190
  db: AsyncSession = Depends(get_async_db),
184
191
  ) -> list[HistoryRunReadAggregated]:
185
192
  # Access control
186
- await get_wftask_check_owner(
193
+ await get_wftask_check_access(
187
194
  project_id=project_id,
188
195
  dataset_id=dataset_id,
189
196
  workflowtask_id=workflowtask_id,
190
197
  user_id=user.id,
198
+ required_permissions=ProjectPermissions.READ,
191
199
  db=db,
192
200
  )
193
201
 
@@ -276,11 +284,12 @@ async def get_history_run_units(
276
284
  pagination: PaginationRequest = Depends(get_pagination_params),
277
285
  ) -> PaginationResponse[HistoryUnitRead]:
278
286
  # Access control
279
- await get_wftask_check_owner(
287
+ await get_wftask_check_access(
280
288
  project_id=project_id,
281
289
  dataset_id=dataset_id,
282
290
  workflowtask_id=workflowtask_id,
283
291
  user_id=user.id,
292
+ required_permissions=ProjectPermissions.READ,
284
293
  db=db,
285
294
  )
286
295
 
@@ -335,11 +344,12 @@ async def get_history_images(
335
344
  pagination: PaginationRequest = Depends(get_pagination_params),
336
345
  ) -> ImagePage:
337
346
  # Access control and object retrieval
338
- wftask = await get_wftask_check_owner(
347
+ wftask = await get_wftask_check_access(
339
348
  project_id=project_id,
340
349
  dataset_id=dataset_id,
341
350
  workflowtask_id=workflowtask_id,
342
351
  user_id=user.id,
352
+ required_permissions=ProjectPermissions.READ,
343
353
  db=db,
344
354
  )
345
355
  res = await _verify_workflow_and_dataset_access(
@@ -347,6 +357,7 @@ async def get_history_images(
347
357
  workflow_id=wftask.workflow_id,
348
358
  dataset_id=dataset_id,
349
359
  user_id=user.id,
360
+ required_permissions=ProjectPermissions.READ,
350
361
  db=db,
351
362
  )
352
363
  dataset = res["dataset"]
@@ -416,11 +427,12 @@ async def get_image_log(
416
427
  db: AsyncSession = Depends(get_async_db),
417
428
  ) -> JSONResponse:
418
429
  # Access control
419
- wftask = await get_wftask_check_owner(
430
+ wftask = await get_wftask_check_access(
420
431
  project_id=project_id,
421
432
  dataset_id=request_data.dataset_id,
422
433
  workflowtask_id=request_data.workflowtask_id,
423
434
  user_id=user.id,
435
+ required_permissions=ProjectPermissions.READ,
424
436
  db=db,
425
437
  )
426
438
 
@@ -444,11 +456,20 @@ async def get_image_log(
444
456
  db=db,
445
457
  )
446
458
 
459
+ # Get job.working_dir
460
+ res = await db.execute(
461
+ select(JobV2.working_dir)
462
+ .join(HistoryRun, HistoryRun.job_id == JobV2.id)
463
+ .where(HistoryRun.id == history_unit.history_run_id)
464
+ )
465
+ job_working_dir = res.scalar_one_or_none()
466
+
447
467
  # Get log or placeholder text
448
468
  log = read_log_file(
449
469
  logfile=history_unit.logfile,
450
- wftask=wftask,
470
+ task_name=wftask.task.name,
451
471
  dataset_id=request_data.dataset_id,
472
+ job_working_dir=job_working_dir,
452
473
  )
453
474
  return JSONResponse(content=log)
454
475
 
@@ -464,11 +485,12 @@ async def get_history_unit_log(
464
485
  db: AsyncSession = Depends(get_async_db),
465
486
  ) -> JSONResponse:
466
487
  # Access control
467
- wftask = await get_wftask_check_owner(
488
+ wftask = await get_wftask_check_access(
468
489
  project_id=project_id,
469
490
  dataset_id=dataset_id,
470
491
  workflowtask_id=workflowtask_id,
471
492
  user_id=user.id,
493
+ required_permissions=ProjectPermissions.READ,
472
494
  db=db,
473
495
  )
474
496
 
@@ -495,11 +517,14 @@ async def get_history_unit_log(
495
517
  workflowtask_id=workflowtask_id,
496
518
  )
497
519
 
520
+ job = await db.get(JobV2, history_run.job_id)
521
+
498
522
  # Get log or placeholder text
499
523
  log = read_log_file(
500
524
  logfile=history_unit.logfile,
501
- wftask=wftask,
525
+ task_name=wftask.task.name,
502
526
  dataset_id=dataset_id,
527
+ job_working_dir=job.working_dir,
503
528
  )
504
529
  return JSONResponse(content=log)
505
530
 
@@ -516,10 +541,11 @@ async def get_dataset_history(
516
541
  timestamp.
517
542
  """
518
543
  # Access control
519
- await _get_dataset_check_owner(
544
+ await _get_dataset_check_access(
520
545
  project_id=project_id,
521
546
  dataset_id=dataset_id,
522
547
  user_id=user.id,
548
+ required_permissions=ProjectPermissions.READ,
523
549
  db=db,
524
550
  )
525
551
 
@@ -8,15 +8,15 @@ from pydantic import Field
8
8
  from sqlalchemy.orm.attributes import flag_modified
9
9
  from sqlmodel import delete
10
10
 
11
- from ._aux_functions import _get_dataset_check_owner
12
11
  from fractal_server.app.db import AsyncSession
13
12
  from fractal_server.app.db import get_async_db
14
13
  from fractal_server.app.models import HistoryImageCache
15
14
  from fractal_server.app.models import UserOAuth
16
15
  from fractal_server.app.routes.auth import current_user_act_ver_prof
17
- from fractal_server.app.routes.pagination import get_pagination_params
18
16
  from fractal_server.app.routes.pagination import PaginationRequest
19
17
  from fractal_server.app.routes.pagination import PaginationResponse
18
+ from fractal_server.app.routes.pagination import get_pagination_params
19
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
20
20
  from fractal_server.images import SingleImage
21
21
  from fractal_server.images import SingleImageUpdate
22
22
  from fractal_server.images.tools import aggregate_attributes
@@ -27,6 +27,8 @@ from fractal_server.types import AttributeFilters
27
27
  from fractal_server.types import ImageAttributeValue
28
28
  from fractal_server.types import TypeFilters
29
29
 
30
+ from ._aux_functions import _get_dataset_check_access
31
+
30
32
  router = APIRouter()
31
33
 
32
34
 
@@ -63,8 +65,12 @@ async def post_new_image(
63
65
  user: UserOAuth = Depends(current_user_act_ver_prof),
64
66
  db: AsyncSession = Depends(get_async_db),
65
67
  ) -> Response:
66
- output = await _get_dataset_check_owner(
67
- project_id=project_id, dataset_id=dataset_id, user_id=user.id, db=db
68
+ output = await _get_dataset_check_access(
69
+ project_id=project_id,
70
+ dataset_id=dataset_id,
71
+ user_id=user.id,
72
+ required_permissions=ProjectPermissions.WRITE,
73
+ db=db,
68
74
  )
69
75
  dataset = output["dataset"]
70
76
 
@@ -118,8 +124,12 @@ async def query_dataset_images(
118
124
  page = pagination.page
119
125
  page_size = pagination.page_size
120
126
 
121
- output = await _get_dataset_check_owner(
122
- project_id=project_id, dataset_id=dataset_id, user_id=user.id, db=db
127
+ output = await _get_dataset_check_access(
128
+ project_id=project_id,
129
+ dataset_id=dataset_id,
130
+ user_id=user.id,
131
+ required_permissions=ProjectPermissions.READ,
132
+ db=db,
123
133
  )
124
134
  dataset = output["dataset"]
125
135
  images = dataset.images
@@ -186,8 +196,12 @@ async def delete_dataset_images(
186
196
  user: UserOAuth = Depends(current_user_act_ver_prof),
187
197
  db: AsyncSession = Depends(get_async_db),
188
198
  ) -> Response:
189
- output = await _get_dataset_check_owner(
190
- project_id=project_id, dataset_id=dataset_id, user_id=user.id, db=db
199
+ output = await _get_dataset_check_access(
200
+ project_id=project_id,
201
+ dataset_id=dataset_id,
202
+ user_id=user.id,
203
+ required_permissions=ProjectPermissions.WRITE,
204
+ db=db,
191
205
  )
192
206
  dataset = output["dataset"]
193
207
 
@@ -230,10 +244,11 @@ async def patch_dataset_image(
230
244
  user: UserOAuth = Depends(current_user_act_ver_prof),
231
245
  db: AsyncSession = Depends(get_async_db),
232
246
  ):
233
- output = await _get_dataset_check_owner(
247
+ output = await _get_dataset_check_access(
234
248
  project_id=project_id,
235
249
  dataset_id=dataset_id,
236
250
  user_id=user.id,
251
+ required_permissions=ProjectPermissions.WRITE,
237
252
  db=db,
238
253
  )
239
254
  db_dataset = output["dataset"]
@@ -10,21 +10,23 @@ from fastapi import status
10
10
  from fastapi.responses import StreamingResponse
11
11
  from sqlmodel import select
12
12
 
13
- from .....zip_tools import _zip_folder_to_byte_stream_iterator
14
- from ....db import AsyncSession
15
- from ....db import get_async_db
16
- from ....models.v2 import JobV2
17
- from ....models.v2 import ProjectV2
18
- from ....schemas.v2 import JobReadV2
19
- from ....schemas.v2 import JobStatusTypeV2
20
- from ...aux._job import _write_shutdown_file
21
- from ...aux._runner import _check_shutdown_is_supported
22
- from ._aux_functions import _get_job_check_owner
23
- from ._aux_functions import _get_project_check_owner
24
- from ._aux_functions import _get_workflow_check_owner
13
+ from fractal_server.app.db import AsyncSession
14
+ from fractal_server.app.db import get_async_db
25
15
  from fractal_server.app.models import UserOAuth
16
+ from fractal_server.app.models.v2 import JobV2
17
+ from fractal_server.app.models.v2 import LinkUserProjectV2
26
18
  from fractal_server.app.routes.auth import current_user_act_ver_prof
19
+ from fractal_server.app.routes.aux._job import _write_shutdown_file
20
+ from fractal_server.app.routes.aux._runner import _check_shutdown_is_supported
21
+ from fractal_server.app.schemas.v2 import JobRead
22
+ from fractal_server.app.schemas.v2 import JobStatusType
23
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
27
24
  from fractal_server.runner.filenames import WORKFLOW_LOG_FILENAME
25
+ from fractal_server.zip_tools import _zip_folder_to_byte_stream_iterator
26
+
27
+ from ._aux_functions import _get_job_check_access
28
+ from ._aux_functions import _get_project_check_access
29
+ from ._aux_functions import _get_workflow_check_access
28
30
 
29
31
 
30
32
  # https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread
@@ -37,19 +39,22 @@ async def zip_folder_threaded(folder: str) -> Iterator[bytes]:
37
39
  router = APIRouter()
38
40
 
39
41
 
40
- @router.get("/job/", response_model=list[JobReadV2])
42
+ @router.get("/job/", response_model=list[JobRead])
41
43
  async def get_user_jobs(
42
44
  user: UserOAuth = Depends(current_user_act_ver_prof),
43
45
  log: bool = True,
44
46
  db: AsyncSession = Depends(get_async_db),
45
- ) -> list[JobReadV2]:
47
+ ) -> list[JobRead]:
46
48
  """
47
49
  Returns all the jobs of the current user
48
50
  """
49
51
  stm = (
50
52
  select(JobV2)
51
- .join(ProjectV2)
52
- .where(ProjectV2.user_list.any(UserOAuth.id == user.id))
53
+ .join(
54
+ LinkUserProjectV2, LinkUserProjectV2.project_id == JobV2.project_id
55
+ )
56
+ .where(LinkUserProjectV2.user_id == user.id)
57
+ .where(LinkUserProjectV2.is_owner.is_(True))
53
58
  )
54
59
  res = await db.execute(stm)
55
60
  job_list = res.scalars().all()
@@ -63,19 +68,23 @@ async def get_user_jobs(
63
68
 
64
69
  @router.get(
65
70
  "/project/{project_id}/workflow/{workflow_id}/job/",
66
- response_model=list[JobReadV2],
71
+ response_model=list[JobRead],
67
72
  )
68
73
  async def get_workflow_jobs(
69
74
  project_id: int,
70
75
  workflow_id: int,
71
76
  user: UserOAuth = Depends(current_user_act_ver_prof),
72
77
  db: AsyncSession = Depends(get_async_db),
73
- ) -> list[JobReadV2] | None:
78
+ ) -> list[JobRead] | None:
74
79
  """
75
80
  Returns all the jobs related to a specific workflow
76
81
  """
77
- await _get_workflow_check_owner(
78
- project_id=project_id, workflow_id=workflow_id, user_id=user.id, db=db
82
+ await _get_workflow_check_access(
83
+ project_id=project_id,
84
+ workflow_id=workflow_id,
85
+ user_id=user.id,
86
+ required_permissions=ProjectPermissions.READ,
87
+ db=db,
79
88
  )
80
89
  stm = select(JobV2).where(JobV2.workflow_id == workflow_id)
81
90
  res = await db.execute(stm)
@@ -90,9 +99,13 @@ async def get_latest_job(
90
99
  dataset_id: int,
91
100
  user: UserOAuth = Depends(current_user_act_ver_prof),
92
101
  db: AsyncSession = Depends(get_async_db),
93
- ) -> JobReadV2:
94
- await _get_workflow_check_owner(
95
- project_id=project_id, workflow_id=workflow_id, user_id=user.id, db=db
102
+ ) -> JobRead:
103
+ await _get_workflow_check_access(
104
+ project_id=project_id,
105
+ workflow_id=workflow_id,
106
+ user_id=user.id,
107
+ required_permissions=ProjectPermissions.READ,
108
+ db=db,
96
109
  )
97
110
  stm = (
98
111
  select(JobV2)
@@ -114,7 +127,7 @@ async def get_latest_job(
114
127
 
115
128
  @router.get(
116
129
  "/project/{project_id}/job/{job_id}/",
117
- response_model=JobReadV2,
130
+ response_model=JobRead,
118
131
  )
119
132
  async def read_job(
120
133
  project_id: int,
@@ -122,21 +135,22 @@ async def read_job(
122
135
  show_tmp_logs: bool = False,
123
136
  user: UserOAuth = Depends(current_user_act_ver_prof),
124
137
  db: AsyncSession = Depends(get_async_db),
125
- ) -> JobReadV2 | None:
138
+ ) -> JobRead | None:
126
139
  """
127
140
  Return info on an existing job
128
141
  """
129
142
 
130
- output = await _get_job_check_owner(
143
+ output = await _get_job_check_access(
131
144
  project_id=project_id,
132
145
  job_id=job_id,
133
146
  user_id=user.id,
147
+ required_permissions=ProjectPermissions.READ,
134
148
  db=db,
135
149
  )
136
150
  job = output["job"]
137
151
  await db.close()
138
152
 
139
- if show_tmp_logs and (job.status == JobStatusTypeV2.SUBMITTED):
153
+ if show_tmp_logs and (job.status == JobStatusType.SUBMITTED):
140
154
  try:
141
155
  with open(f"{job.working_dir}/{WORKFLOW_LOG_FILENAME}") as f:
142
156
  job.log = f.read()
@@ -159,10 +173,11 @@ async def download_job_logs(
159
173
  """
160
174
  Download zipped job folder
161
175
  """
162
- output = await _get_job_check_owner(
176
+ output = await _get_job_check_access(
163
177
  project_id=project_id,
164
178
  job_id=job_id,
165
179
  user_id=user.id,
180
+ required_permissions=ProjectPermissions.READ,
166
181
  db=db,
167
182
  )
168
183
  job = output["job"]
@@ -179,19 +194,22 @@ async def download_job_logs(
179
194
 
180
195
  @router.get(
181
196
  "/project/{project_id}/job/",
182
- response_model=list[JobReadV2],
197
+ response_model=list[JobRead],
183
198
  )
184
199
  async def get_job_list(
185
200
  project_id: int,
186
201
  user: UserOAuth = Depends(current_user_act_ver_prof),
187
202
  log: bool = True,
188
203
  db: AsyncSession = Depends(get_async_db),
189
- ) -> list[JobReadV2] | None:
204
+ ) -> list[JobRead] | None:
190
205
  """
191
206
  Get job list for given project
192
207
  """
193
- project = await _get_project_check_owner(
194
- project_id=project_id, user_id=user.id, db=db
208
+ project = await _get_project_check_access(
209
+ project_id=project_id,
210
+ user_id=user.id,
211
+ required_permissions=ProjectPermissions.READ,
212
+ db=db,
195
213
  )
196
214
 
197
215
  stm = select(JobV2).where(JobV2.project_id == project.id)
@@ -222,10 +240,11 @@ async def stop_job(
222
240
  _check_shutdown_is_supported()
223
241
 
224
242
  # Get job from DB
225
- output = await _get_job_check_owner(
243
+ output = await _get_job_check_access(
226
244
  project_id=project_id,
227
245
  job_id=job_id,
228
246
  user_id=user.id,
247
+ required_permissions=ProjectPermissions.EXECUTE,
229
248
  db=db,
230
249
  )
231
250
  job = output["job"]
@@ -5,21 +5,23 @@ from fastapi.responses import JSONResponse
5
5
  from pydantic import BaseModel
6
6
  from pydantic import Field
7
7
 
8
- from ._aux_functions import _get_dataset_check_owner
9
- from ._aux_functions import _get_workflow_task_check_owner
10
- from .images import ImageQuery
11
8
  from fractal_server.app.db import AsyncSession
12
9
  from fractal_server.app.db import get_async_db
13
10
  from fractal_server.app.models import UserOAuth
14
11
  from fractal_server.app.routes.auth import current_user_act_ver_prof
15
12
  from fractal_server.app.schemas.v2 import HistoryUnitStatus
16
13
  from fractal_server.app.schemas.v2 import TaskType
17
- from fractal_server.images.status_tools import enrich_images_unsorted_async
14
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
18
15
  from fractal_server.images.status_tools import IMAGE_STATUS_KEY
16
+ from fractal_server.images.status_tools import enrich_images_unsorted_async
19
17
  from fractal_server.images.tools import aggregate_types
20
18
  from fractal_server.images.tools import filter_image_list
21
19
  from fractal_server.types import AttributeFilters
22
20
 
21
+ from ._aux_functions import _get_dataset_check_access
22
+ from ._aux_functions import _get_workflow_task_check_access
23
+ from .images import ImageQuery
24
+
23
25
  router = APIRouter()
24
26
 
25
27
 
@@ -36,8 +38,12 @@ async def verify_unique_types(
36
38
  db: AsyncSession = Depends(get_async_db),
37
39
  ) -> list[str]:
38
40
  # Get dataset
39
- output = await _get_dataset_check_owner(
40
- project_id=project_id, dataset_id=dataset_id, user_id=user.id, db=db
41
+ output = await _get_dataset_check_access(
42
+ project_id=project_id,
43
+ dataset_id=dataset_id,
44
+ user_id=user.id,
45
+ required_permissions=ProjectPermissions.READ,
46
+ db=db,
41
47
  )
42
48
  dataset = output["dataset"]
43
49
 
@@ -96,11 +102,12 @@ async def check_non_processed_images(
96
102
  user: UserOAuth = Depends(current_user_act_ver_prof),
97
103
  db: AsyncSession = Depends(get_async_db),
98
104
  ) -> JSONResponse:
99
- db_workflow_task, db_workflow = await _get_workflow_task_check_owner(
105
+ db_workflow_task, db_workflow = await _get_workflow_task_check_access(
100
106
  project_id=project_id,
101
107
  workflow_task_id=workflowtask_id,
102
108
  workflow_id=workflow_id,
103
109
  user_id=user.id,
110
+ required_permissions=ProjectPermissions.READ,
104
111
  db=db,
105
112
  )
106
113
 
@@ -120,10 +127,11 @@ async def check_non_processed_images(
120
127
  # Skip check if previous task is converter
121
128
  return JSONResponse(status_code=200, content=[])
122
129
 
123
- res = await _get_dataset_check_owner(
130
+ res = await _get_dataset_check_access(
124
131
  project_id=project_id,
125
132
  dataset_id=dataset_id,
126
133
  user_id=user.id,
134
+ required_permissions=ProjectPermissions.READ,
127
135
  db=db,
128
136
  )
129
137
  dataset = res["dataset"]