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
@@ -8,45 +8,49 @@ from fastapi import status
8
8
  from pydantic import BaseModel
9
9
  from sqlmodel import select
10
10
 
11
- from ....db import AsyncSession
12
- from ....db import get_async_db
13
- from ....models.v2 import JobV2
14
- from ....models.v2 import ProjectV2
15
- from ....models.v2 import WorkflowV2
16
- from ....schemas.v2 import WorkflowCreateV2
17
- from ....schemas.v2 import WorkflowExportV2
18
- from ....schemas.v2 import WorkflowReadV2
19
- from ....schemas.v2 import WorkflowReadV2WithWarnings
20
- from ....schemas.v2 import WorkflowUpdateV2
21
- from ._aux_functions import _check_workflow_exists
22
- from ._aux_functions import _get_project_check_owner
23
- from ._aux_functions import _get_submitted_jobs_statement
24
- from ._aux_functions import _get_workflow_check_owner
25
- from ._aux_functions import _workflow_has_submitted_job
26
- from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
11
+ from fractal_server.app.db import AsyncSession
12
+ from fractal_server.app.db import get_async_db
27
13
  from fractal_server.app.models import UserOAuth
14
+ from fractal_server.app.models.v2 import JobV2
28
15
  from fractal_server.app.models.v2 import TaskGroupV2
16
+ from fractal_server.app.models.v2 import WorkflowV2
29
17
  from fractal_server.app.routes.auth import current_user_act_ver_prof
18
+ from fractal_server.app.schemas.v2 import WorkflowCreate
19
+ from fractal_server.app.schemas.v2 import WorkflowExport
20
+ from fractal_server.app.schemas.v2 import WorkflowRead
21
+ from fractal_server.app.schemas.v2 import WorkflowReadWithWarnings
22
+ from fractal_server.app.schemas.v2 import WorkflowUpdate
23
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
30
24
  from fractal_server.images.tools import merge_type_filters
31
25
 
26
+ from ._aux_functions import _check_workflow_exists
27
+ from ._aux_functions import _get_project_check_access
28
+ from ._aux_functions import _get_submitted_jobs_statement
29
+ from ._aux_functions import _get_workflow_check_access
30
+ from ._aux_functions import _workflow_has_submitted_job
31
+ from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
32
+
32
33
  router = APIRouter()
33
34
 
34
35
 
35
36
  @router.get(
36
37
  "/project/{project_id}/workflow/",
37
- response_model=list[WorkflowReadV2],
38
+ response_model=list[WorkflowRead],
38
39
  )
39
40
  async def get_workflow_list(
40
41
  project_id: int,
41
42
  user: UserOAuth = Depends(current_user_act_ver_prof),
42
43
  db: AsyncSession = Depends(get_async_db),
43
- ) -> list[WorkflowReadV2] | None:
44
+ ) -> list[WorkflowRead] | None:
44
45
  """
45
46
  Get workflow list for given project
46
47
  """
47
48
  # Access control
48
- project = await _get_project_check_owner(
49
- project_id=project_id, user_id=user.id, db=db
49
+ project = await _get_project_check_access(
50
+ project_id=project_id,
51
+ user_id=user.id,
52
+ required_permissions=ProjectPermissions.READ,
53
+ db=db,
50
54
  )
51
55
  # Find workflows of the current project. Note: this select/where approach
52
56
  # has much better scaling than refreshing all elements of
@@ -59,20 +63,23 @@ async def get_workflow_list(
59
63
 
60
64
  @router.post(
61
65
  "/project/{project_id}/workflow/",
62
- response_model=WorkflowReadV2,
66
+ response_model=WorkflowRead,
63
67
  status_code=status.HTTP_201_CREATED,
64
68
  )
65
69
  async def create_workflow(
66
70
  project_id: int,
67
- workflow: WorkflowCreateV2,
71
+ workflow: WorkflowCreate,
68
72
  user: UserOAuth = Depends(current_user_act_ver_prof),
69
73
  db: AsyncSession = Depends(get_async_db),
70
- ) -> WorkflowReadV2 | None:
74
+ ) -> WorkflowRead | None:
71
75
  """
72
76
  Create a workflow, associate to a project
73
77
  """
74
- await _get_project_check_owner(
75
- project_id=project_id, user_id=user.id, db=db
78
+ await _get_project_check_access(
79
+ project_id=project_id,
80
+ user_id=user.id,
81
+ required_permissions=ProjectPermissions.WRITE,
82
+ db=db,
76
83
  )
77
84
  await _check_workflow_exists(
78
85
  name=workflow.name, project_id=project_id, db=db
@@ -88,22 +95,23 @@ async def create_workflow(
88
95
 
89
96
  @router.get(
90
97
  "/project/{project_id}/workflow/{workflow_id}/",
91
- response_model=WorkflowReadV2WithWarnings,
98
+ response_model=WorkflowReadWithWarnings,
92
99
  )
93
100
  async def read_workflow(
94
101
  project_id: int,
95
102
  workflow_id: int,
96
103
  user: UserOAuth = Depends(current_user_act_ver_prof),
97
104
  db: AsyncSession = Depends(get_async_db),
98
- ) -> WorkflowReadV2WithWarnings | None:
105
+ ) -> WorkflowReadWithWarnings | None:
99
106
  """
100
107
  Get info on an existing workflow
101
108
  """
102
109
 
103
- workflow = await _get_workflow_check_owner(
110
+ workflow = await _get_workflow_check_access(
104
111
  project_id=project_id,
105
112
  workflow_id=workflow_id,
106
113
  user_id=user.id,
114
+ required_permissions=ProjectPermissions.READ,
107
115
  db=db,
108
116
  )
109
117
 
@@ -121,22 +129,23 @@ async def read_workflow(
121
129
 
122
130
  @router.patch(
123
131
  "/project/{project_id}/workflow/{workflow_id}/",
124
- response_model=WorkflowReadV2WithWarnings,
132
+ response_model=WorkflowReadWithWarnings,
125
133
  )
126
134
  async def update_workflow(
127
135
  project_id: int,
128
136
  workflow_id: int,
129
- patch: WorkflowUpdateV2,
137
+ patch: WorkflowUpdate,
130
138
  user: UserOAuth = Depends(current_user_act_ver_prof),
131
139
  db: AsyncSession = Depends(get_async_db),
132
- ) -> WorkflowReadV2WithWarnings | None:
140
+ ) -> WorkflowReadWithWarnings | None:
133
141
  """
134
142
  Edit a workflow
135
143
  """
136
- workflow = await _get_workflow_check_owner(
144
+ workflow = await _get_workflow_check_access(
137
145
  project_id=project_id,
138
146
  workflow_id=workflow_id,
139
147
  user_id=user.id,
148
+ required_permissions=ProjectPermissions.WRITE,
140
149
  db=db,
141
150
  )
142
151
 
@@ -208,10 +217,11 @@ async def delete_workflow(
208
217
  Delete a workflow
209
218
  """
210
219
 
211
- workflow = await _get_workflow_check_owner(
220
+ workflow = await _get_workflow_check_access(
212
221
  project_id=project_id,
213
222
  workflow_id=workflow_id,
214
223
  user_id=user.id,
224
+ required_permissions=ProjectPermissions.WRITE,
215
225
  db=db,
216
226
  )
217
227
 
@@ -241,21 +251,22 @@ async def delete_workflow(
241
251
 
242
252
  @router.get(
243
253
  "/project/{project_id}/workflow/{workflow_id}/export/",
244
- response_model=WorkflowExportV2,
254
+ response_model=WorkflowExport,
245
255
  )
246
256
  async def export_workflow(
247
257
  project_id: int,
248
258
  workflow_id: int,
249
259
  user: UserOAuth = Depends(current_user_act_ver_prof),
250
260
  db: AsyncSession = Depends(get_async_db),
251
- ) -> WorkflowExportV2 | None:
261
+ ) -> WorkflowExport | None:
252
262
  """
253
263
  Export an existing workflow, after stripping all IDs
254
264
  """
255
- workflow = await _get_workflow_check_owner(
265
+ workflow = await _get_workflow_check_access(
256
266
  project_id=project_id,
257
267
  workflow_id=workflow_id,
258
268
  user_id=user.id,
269
+ required_permissions=ProjectPermissions.READ,
259
270
  db=db,
260
271
  )
261
272
  wf_task_list = []
@@ -268,30 +279,13 @@ async def export_workflow(
268
279
  name=wftask.task.name,
269
280
  )
270
281
 
271
- wf = WorkflowExportV2(
282
+ wf = WorkflowExport(
272
283
  **workflow.model_dump(),
273
284
  task_list=wf_task_list,
274
285
  )
275
286
  return wf
276
287
 
277
288
 
278
- @router.get("/workflow/", response_model=list[WorkflowReadV2])
279
- async def get_user_workflows(
280
- user: UserOAuth = Depends(current_user_act_ver_prof),
281
- db: AsyncSession = Depends(get_async_db),
282
- ) -> list[WorkflowReadV2]:
283
- """
284
- Returns all the workflows of the current user
285
- """
286
- stm = select(WorkflowV2)
287
- stm = stm.join(ProjectV2).where(
288
- ProjectV2.user_list.any(UserOAuth.id == user.id)
289
- )
290
- res = await db.execute(stm)
291
- workflow_list = res.scalars().all()
292
- return workflow_list
293
-
294
-
295
289
  class WorkflowTaskTypeFiltersInfo(BaseModel):
296
290
  workflowtask_id: int
297
291
  current_type_filters: dict[str, bool]
@@ -310,10 +304,11 @@ async def get_workflow_type_filters(
310
304
  Get info on type/type-filters flow for a workflow.
311
305
  """
312
306
 
313
- workflow = await _get_workflow_check_owner(
307
+ workflow = await _get_workflow_check_access(
314
308
  project_id=project_id,
315
309
  workflow_id=workflow_id,
316
310
  user_id=user.id,
311
+ required_permissions=ProjectPermissions.READ,
317
312
  db=db,
318
313
  )
319
314
 
@@ -5,23 +5,13 @@ from fastapi import status
5
5
  from sqlmodel import or_
6
6
  from sqlmodel import select
7
7
 
8
- from ....db import AsyncSession
9
- from ....db import get_async_db
10
- from ....models.v2 import TaskV2
11
- from ....models.v2 import WorkflowV2
12
- from ....schemas.v2 import TaskImportV2Legacy
13
- from ....schemas.v2 import WorkflowImportV2
14
- from ....schemas.v2 import WorkflowReadV2WithWarnings
15
- from ....schemas.v2 import WorkflowTaskCreateV2
16
- from ._aux_functions import _check_workflow_exists
17
- from ._aux_functions import _get_project_check_owner
18
- from ._aux_functions import _get_user_resource_id
19
- from ._aux_functions import _workflow_insert_task
20
- from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
21
- from ._aux_functions_tasks import _check_type_filters_compatibility
8
+ from fractal_server.app.db import AsyncSession
9
+ from fractal_server.app.db import get_async_db
22
10
  from fractal_server.app.models import LinkUserGroup
23
11
  from fractal_server.app.models import UserOAuth
24
12
  from fractal_server.app.models.v2 import TaskGroupV2
13
+ from fractal_server.app.models.v2 import TaskV2
14
+ from fractal_server.app.models.v2 import WorkflowV2
25
15
  from fractal_server.app.routes.api.v2._aux_task_group_disambiguation import (
26
16
  _disambiguate_task_groups,
27
17
  )
@@ -29,9 +19,21 @@ from fractal_server.app.routes.auth import current_user_act_ver_prof
29
19
  from fractal_server.app.routes.auth._aux_auth import (
30
20
  _get_default_usergroup_id_or_none,
31
21
  )
32
- from fractal_server.app.schemas.v2 import TaskImportV2
22
+ from fractal_server.app.schemas.v2 import TaskImport
23
+ from fractal_server.app.schemas.v2 import TaskImportLegacy
24
+ from fractal_server.app.schemas.v2 import WorkflowImport
25
+ from fractal_server.app.schemas.v2 import WorkflowReadWithWarnings
26
+ from fractal_server.app.schemas.v2 import WorkflowTaskCreate
27
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
33
28
  from fractal_server.logger import set_logger
34
29
 
30
+ from ._aux_functions import _check_workflow_exists
31
+ from ._aux_functions import _get_project_check_access
32
+ from ._aux_functions import _get_user_resource_id
33
+ from ._aux_functions import _workflow_insert_task
34
+ from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
35
+ from ._aux_functions_tasks import _check_type_filters_compatibility
36
+
35
37
  router = APIRouter()
36
38
 
37
39
 
@@ -64,7 +66,7 @@ async def _get_user_accessible_taskgroups(
64
66
  )
65
67
  res = await db.execute(stm)
66
68
  accessible_task_groups = res.scalars().all()
67
- logger.info(
69
+ logger.debug(
68
70
  f"Found {len(accessible_task_groups)} accessible "
69
71
  f"task groups for {user_id=}."
70
72
  )
@@ -99,7 +101,7 @@ async def _get_task_by_source(
99
101
 
100
102
  async def _get_task_by_taskimport(
101
103
  *,
102
- task_import: TaskImportV2,
104
+ task_import: TaskImport,
103
105
  task_groups_list: list[TaskGroupV2],
104
106
  user_id: int,
105
107
  default_group_id: int | None,
@@ -119,7 +121,7 @@ async def _get_task_by_taskimport(
119
121
  `id` of the matching task, or `None`.
120
122
  """
121
123
 
122
- logger.info(f"[_get_task_by_taskimport] START, {task_import=}")
124
+ logger.debug(f"[_get_task_by_taskimport] START, {task_import=}")
123
125
 
124
126
  # Filter by `pkg_name` and by presence of a task with given `name`.
125
127
  matching_task_groups = [
@@ -127,12 +129,11 @@ async def _get_task_by_taskimport(
127
129
  for task_group in task_groups_list
128
130
  if (
129
131
  task_group.pkg_name == task_import.pkg_name
130
- and task_import.name
131
- in [task.name for task in task_group.task_list]
132
+ and task_import.name in [task.name for task in task_group.task_list]
132
133
  )
133
134
  ]
134
135
  if len(matching_task_groups) < 1:
135
- logger.info(
136
+ logger.debug(
136
137
  "[_get_task_by_taskimport] "
137
138
  f"No task group with {task_import.pkg_name=} "
138
139
  f"and a task with {task_import.name=}."
@@ -142,15 +143,13 @@ async def _get_task_by_taskimport(
142
143
  # Determine target `version`
143
144
  # Note that task_import.version cannot be "", due to a validator
144
145
  if task_import.version is None:
145
- logger.info(
146
+ logger.debug(
146
147
  "[_get_task_by_taskimport] "
147
148
  "No version requested, looking for latest."
148
149
  )
149
- latest_task = max(
150
- matching_task_groups, key=lambda tg: tg.version or ""
151
- )
150
+ latest_task = max(matching_task_groups, key=lambda tg: tg.version or "")
152
151
  version = latest_task.version
153
- logger.info(
152
+ logger.debug(
154
153
  f"[_get_task_by_taskimport] Latest version set to {version}."
155
154
  )
156
155
  else:
@@ -162,19 +161,19 @@ async def _get_task_by_taskimport(
162
161
  )
163
162
 
164
163
  if len(final_matching_task_groups) < 1:
165
- logger.info(
164
+ logger.debug(
166
165
  "[_get_task_by_taskimport] "
167
166
  "No task group left after filtering by version."
168
167
  )
169
168
  return None
170
169
  elif len(final_matching_task_groups) == 1:
171
170
  final_task_group = final_matching_task_groups[0]
172
- logger.info(
171
+ logger.debug(
173
172
  "[_get_task_by_taskimport] "
174
173
  "Found a single task group, after filtering by version."
175
174
  )
176
175
  else:
177
- logger.info(
176
+ logger.debug(
178
177
  "[_get_task_by_taskimport] "
179
178
  f"Found {len(final_matching_task_groups)} task groups, "
180
179
  "after filtering by version."
@@ -186,7 +185,7 @@ async def _get_task_by_taskimport(
186
185
  default_group_id=default_group_id,
187
186
  )
188
187
  if final_task_group is None:
189
- logger.info(
188
+ logger.debug(
190
189
  "[_get_task_by_taskimport] Disambiguation returned None."
191
190
  )
192
191
  return None
@@ -201,22 +200,22 @@ async def _get_task_by_taskimport(
201
200
  None,
202
201
  )
203
202
 
204
- logger.info(f"[_get_task_by_taskimport] END, {task_import=}, {task_id=}.")
203
+ logger.debug(f"[_get_task_by_taskimport] END, {task_import=}, {task_id=}.")
205
204
 
206
205
  return task_id
207
206
 
208
207
 
209
208
  @router.post(
210
209
  "/project/{project_id}/workflow/import/",
211
- response_model=WorkflowReadV2WithWarnings,
210
+ response_model=WorkflowReadWithWarnings,
212
211
  status_code=status.HTTP_201_CREATED,
213
212
  )
214
213
  async def import_workflow(
215
214
  project_id: int,
216
- workflow_import: WorkflowImportV2,
215
+ workflow_import: WorkflowImport,
217
216
  user: UserOAuth = Depends(current_user_act_ver_prof),
218
217
  db: AsyncSession = Depends(get_async_db),
219
- ) -> WorkflowReadV2WithWarnings:
218
+ ) -> WorkflowReadWithWarnings:
220
219
  """
221
220
  Import an existing workflow into a project and create required objects.
222
221
  """
@@ -224,9 +223,10 @@ async def import_workflow(
224
223
  user_resource_id = await _get_user_resource_id(user_id=user.id, db=db)
225
224
 
226
225
  # Preliminary checks
227
- await _get_project_check_owner(
226
+ await _get_project_check_access(
228
227
  project_id=project_id,
229
228
  user_id=user.id,
229
+ required_permissions=ProjectPermissions.WRITE,
230
230
  db=db,
231
231
  )
232
232
  await _check_workflow_exists(
@@ -246,7 +246,7 @@ async def import_workflow(
246
246
  list_task_ids = []
247
247
  for wf_task in workflow_import.task_list:
248
248
  task_import = wf_task.task
249
- if isinstance(task_import, TaskImportV2Legacy):
249
+ if isinstance(task_import, TaskImportLegacy):
250
250
  task_id = await _get_task_by_source(
251
251
  source=task_import.source,
252
252
  task_groups_list=task_group_list,
@@ -264,7 +264,7 @@ async def import_workflow(
264
264
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
265
265
  detail=f"Could not find a task matching with {wf_task.task}.",
266
266
  )
267
- new_wf_task = WorkflowTaskCreateV2(
267
+ new_wf_task = WorkflowTaskCreate(
268
268
  **wf_task.model_dump(exclude_none=True, exclude={"task"})
269
269
  )
270
270
  list_wf_tasks.append(new_wf_task)
@@ -6,43 +6,49 @@ from fastapi import HTTPException
6
6
  from fastapi import Response
7
7
  from fastapi import status
8
8
 
9
- from ....db import AsyncSession
10
- from ....db import get_async_db
11
- from ._aux_functions import _get_workflow_check_owner
12
- from ._aux_functions import _get_workflow_task_check_owner
9
+ from fractal_server.app.db import AsyncSession
10
+ from fractal_server.app.db import get_async_db
11
+ from fractal_server.app.models import UserOAuth
12
+ from fractal_server.app.routes.auth import current_user_act_ver_prof
13
+ from fractal_server.app.schemas.v2 import TaskType
14
+ from fractal_server.app.schemas.v2 import WorkflowTaskCreate
15
+ from fractal_server.app.schemas.v2 import WorkflowTaskRead
16
+ from fractal_server.app.schemas.v2 import WorkflowTaskUpdate
17
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
18
+
19
+ from ._aux_functions import _get_workflow_check_access
20
+ from ._aux_functions import _get_workflow_task_check_access
13
21
  from ._aux_functions import _workflow_has_submitted_job
14
22
  from ._aux_functions import _workflow_insert_task
15
23
  from ._aux_functions_tasks import _check_type_filters_compatibility
16
24
  from ._aux_functions_tasks import _get_task_read_access
17
- from fractal_server.app.models import UserOAuth
18
- from fractal_server.app.routes.auth import current_user_act_ver_prof
19
- from fractal_server.app.schemas.v2 import TaskType
20
- from fractal_server.app.schemas.v2 import WorkflowTaskCreateV2
21
- from fractal_server.app.schemas.v2 import WorkflowTaskReadV2
22
- from fractal_server.app.schemas.v2 import WorkflowTaskUpdateV2
23
25
 
24
26
  router = APIRouter()
25
27
 
26
28
 
27
29
  @router.post(
28
30
  "/project/{project_id}/workflow/{workflow_id}/wftask/",
29
- response_model=WorkflowTaskReadV2,
31
+ response_model=WorkflowTaskRead,
30
32
  status_code=status.HTTP_201_CREATED,
31
33
  )
32
34
  async def create_workflowtask(
33
35
  project_id: int,
34
36
  workflow_id: int,
35
37
  task_id: int,
36
- wftask: WorkflowTaskCreateV2,
38
+ wftask: WorkflowTaskCreate,
37
39
  user: UserOAuth = Depends(current_user_act_ver_prof),
38
40
  db: AsyncSession = Depends(get_async_db),
39
- ) -> WorkflowTaskReadV2 | None:
41
+ ) -> WorkflowTaskRead | None:
40
42
  """
41
43
  Add a WorkflowTask to a Workflow
42
44
  """
43
45
 
44
- workflow = await _get_workflow_check_owner(
45
- project_id=project_id, workflow_id=workflow_id, user_id=user.id, db=db
46
+ workflow = await _get_workflow_check_access(
47
+ project_id=project_id,
48
+ workflow_id=workflow_id,
49
+ user_id=user.id,
50
+ required_permissions=ProjectPermissions.WRITE,
51
+ db=db,
46
52
  )
47
53
 
48
54
  task = await _get_task_read_access(
@@ -63,10 +69,7 @@ async def create_workflowtask(
63
69
  ),
64
70
  )
65
71
  elif task.type == TaskType.NON_PARALLEL:
66
- if (
67
- wftask.meta_parallel is not None
68
- or wftask.args_parallel is not None
69
- ):
72
+ if wftask.meta_parallel is not None or wftask.args_parallel is not None:
70
73
  raise HTTPException(
71
74
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
72
75
  detail=(
@@ -97,7 +100,7 @@ async def create_workflowtask(
97
100
 
98
101
  @router.get(
99
102
  "/project/{project_id}/workflow/{workflow_id}/wftask/{workflow_task_id}/",
100
- response_model=WorkflowTaskReadV2,
103
+ response_model=WorkflowTaskRead,
101
104
  )
102
105
  async def read_workflowtask(
103
106
  project_id: int,
@@ -106,11 +109,12 @@ async def read_workflowtask(
106
109
  user: UserOAuth = Depends(current_user_act_ver_prof),
107
110
  db: AsyncSession = Depends(get_async_db),
108
111
  ):
109
- workflow_task, _ = await _get_workflow_task_check_owner(
112
+ workflow_task, _ = await _get_workflow_task_check_access(
110
113
  project_id=project_id,
111
114
  workflow_task_id=workflow_task_id,
112
115
  workflow_id=workflow_id,
113
116
  user_id=user.id,
117
+ required_permissions=ProjectPermissions.READ,
114
118
  db=db,
115
119
  )
116
120
  return workflow_task
@@ -118,25 +122,26 @@ async def read_workflowtask(
118
122
 
119
123
  @router.patch(
120
124
  "/project/{project_id}/workflow/{workflow_id}/wftask/{workflow_task_id}/",
121
- response_model=WorkflowTaskReadV2,
125
+ response_model=WorkflowTaskRead,
122
126
  )
123
127
  async def update_workflowtask(
124
128
  project_id: int,
125
129
  workflow_id: int,
126
130
  workflow_task_id: int,
127
- workflow_task_update: WorkflowTaskUpdateV2,
131
+ workflow_task_update: WorkflowTaskUpdate,
128
132
  user: UserOAuth = Depends(current_user_act_ver_prof),
129
133
  db: AsyncSession = Depends(get_async_db),
130
- ) -> WorkflowTaskReadV2 | None:
134
+ ) -> WorkflowTaskRead | None:
131
135
  """
132
136
  Edit a WorkflowTask of a Workflow
133
137
  """
134
138
 
135
- db_wf_task, db_workflow = await _get_workflow_task_check_owner(
139
+ db_wf_task, db_workflow = await _get_workflow_task_check_access(
136
140
  project_id=project_id,
137
141
  workflow_task_id=workflow_task_id,
138
142
  workflow_id=workflow_id,
139
143
  user_id=user.id,
144
+ required_permissions=ProjectPermissions.WRITE,
140
145
  db=db,
141
146
  )
142
147
  if workflow_task_update.type_filters is not None:
@@ -217,11 +222,12 @@ async def delete_workflowtask(
217
222
  Delete a WorkflowTask of a Workflow
218
223
  """
219
224
 
220
- db_workflow_task, db_workflow = await _get_workflow_task_check_owner(
225
+ db_workflow_task, db_workflow = await _get_workflow_task_check_access(
221
226
  project_id=project_id,
222
227
  workflow_task_id=workflow_task_id,
223
228
  workflow_id=workflow_id,
224
229
  user_id=user.id,
230
+ required_permissions=ProjectPermissions.WRITE,
225
231
  db=db,
226
232
  )
227
233
 
@@ -12,7 +12,6 @@ from fractal_server.app.security import get_user_manager
12
12
  from fractal_server.config import get_settings
13
13
  from fractal_server.syringe import Inject
14
14
 
15
-
16
15
  bearer_transport = BearerTransport(tokenUrl="/auth/token/login")
17
16
  cookie_transport = CookieTransport(cookie_samesite="none")
18
17
 
@@ -71,8 +70,7 @@ async def current_user_act_ver_prof(
71
70
  raise HTTPException(
72
71
  status_code=status.HTTP_403_FORBIDDEN,
73
72
  detail=(
74
- f"Forbidden access "
75
- f"({user.is_verified=} {user.profile_id=})."
73
+ f"Forbidden access ({user.is_verified=} {user.profile_id=})."
76
74
  ),
77
75
  )
78
76
  return user