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
@@ -12,21 +12,21 @@ from sqlmodel import select
12
12
 
13
13
  from fractal_server.app.db import AsyncSession
14
14
  from fractal_server.app.db import get_async_db
15
+ from fractal_server.app.models import LinkUserProjectV2
15
16
  from fractal_server.app.models import UserOAuth
16
17
  from fractal_server.app.models.v2 import HistoryRun
17
18
  from fractal_server.app.models.v2 import HistoryUnit
18
19
  from fractal_server.app.models.v2 import JobV2
19
- from fractal_server.app.models.v2 import ProjectV2
20
20
  from fractal_server.app.routes.auth import current_superuser_act
21
21
  from fractal_server.app.routes.aux._job import _write_shutdown_file
22
22
  from fractal_server.app.routes.aux._runner import _check_shutdown_is_supported
23
- from fractal_server.app.routes.pagination import get_pagination_params
24
23
  from fractal_server.app.routes.pagination import PaginationRequest
25
24
  from fractal_server.app.routes.pagination import PaginationResponse
25
+ from fractal_server.app.routes.pagination import get_pagination_params
26
26
  from fractal_server.app.schemas.v2 import HistoryUnitStatus
27
- from fractal_server.app.schemas.v2 import JobReadV2
28
- from fractal_server.app.schemas.v2 import JobStatusTypeV2
29
- from fractal_server.app.schemas.v2 import JobUpdateV2
27
+ from fractal_server.app.schemas.v2 import JobRead
28
+ from fractal_server.app.schemas.v2 import JobStatusType
29
+ from fractal_server.app.schemas.v2 import JobUpdate
30
30
  from fractal_server.runner.filenames import WORKFLOW_LOG_FILENAME
31
31
  from fractal_server.utils import get_timestamp
32
32
  from fractal_server.zip_tools import _zip_folder_to_byte_stream_iterator
@@ -34,14 +34,14 @@ from fractal_server.zip_tools import _zip_folder_to_byte_stream_iterator
34
34
  router = APIRouter()
35
35
 
36
36
 
37
- @router.get("/", response_model=PaginationResponse[JobReadV2])
37
+ @router.get("/", response_model=PaginationResponse[JobRead])
38
38
  async def view_job(
39
39
  id: int | None = None,
40
40
  user_id: int | None = None,
41
41
  project_id: int | None = None,
42
42
  dataset_id: int | None = None,
43
43
  workflow_id: int | None = None,
44
- status: JobStatusTypeV2 | None = None,
44
+ status: JobStatusType | None = None,
45
45
  start_timestamp_min: AwareDatetime | None = None,
46
46
  start_timestamp_max: AwareDatetime | None = None,
47
47
  end_timestamp_min: AwareDatetime | None = None,
@@ -50,12 +50,13 @@ async def view_job(
50
50
  pagination: PaginationRequest = Depends(get_pagination_params),
51
51
  user: UserOAuth = Depends(current_superuser_act),
52
52
  db: AsyncSession = Depends(get_async_db),
53
- ) -> PaginationResponse[JobReadV2]:
53
+ ) -> PaginationResponse[JobRead]:
54
54
  """
55
- Query `ApplyWorkflow` table.
55
+ Query `JobV2` table.
56
56
 
57
57
  Args:
58
58
  id: If not `None`, select a given `applyworkflow.id`.
59
+ user_id:
59
60
  project_id: If not `None`, select a given `applyworkflow.project_id`.
60
61
  dataset_id: If not `None`, select a given
61
62
  `applyworkflow.input_dataset_id`.
@@ -84,11 +85,21 @@ async def view_job(
84
85
  stm = stm.where(JobV2.id == id)
85
86
  stm_count = stm_count.where(JobV2.id == id)
86
87
  if user_id is not None:
87
- stm = stm.join(ProjectV2).where(
88
- ProjectV2.user_list.any(UserOAuth.id == user_id)
88
+ stm = (
89
+ stm.join(
90
+ LinkUserProjectV2,
91
+ LinkUserProjectV2.project_id == JobV2.project_id,
92
+ )
93
+ .where(LinkUserProjectV2.user_id == user_id)
94
+ .where(LinkUserProjectV2.is_owner.is_(True))
89
95
  )
90
- stm_count = stm_count.join(ProjectV2).where(
91
- ProjectV2.user_list.any(UserOAuth.id == user_id)
96
+ stm_count = (
97
+ stm_count.join(
98
+ LinkUserProjectV2,
99
+ LinkUserProjectV2.project_id == JobV2.project_id,
100
+ )
101
+ .where(LinkUserProjectV2.user_id == user_id)
102
+ .where(LinkUserProjectV2.is_owner.is_(True))
92
103
  )
93
104
  if project_id is not None:
94
105
  stm = stm.where(JobV2.project_id == project_id)
@@ -135,21 +146,21 @@ async def view_job(
135
146
  for job in job_list:
136
147
  setattr(job, "log", None)
137
148
 
138
- return PaginationResponse[JobReadV2](
149
+ return dict(
139
150
  total_count=total_count,
140
151
  page_size=page_size,
141
152
  current_page=page,
142
- items=[job.model_dump() for job in job_list],
153
+ items=job_list,
143
154
  )
144
155
 
145
156
 
146
- @router.get("/{job_id}/", response_model=JobReadV2)
157
+ @router.get("/{job_id}/", response_model=JobRead)
147
158
  async def view_single_job(
148
159
  job_id: int,
149
160
  show_tmp_logs: bool = False,
150
161
  user: UserOAuth = Depends(current_superuser_act),
151
162
  db: AsyncSession = Depends(get_async_db),
152
- ) -> JobReadV2:
163
+ ) -> JobRead:
153
164
  job = await db.get(JobV2, job_id)
154
165
  if not job:
155
166
  raise HTTPException(
@@ -158,7 +169,7 @@ async def view_single_job(
158
169
  )
159
170
  await db.close()
160
171
 
161
- if show_tmp_logs and (job.status == JobStatusTypeV2.SUBMITTED):
172
+ if show_tmp_logs and (job.status == JobStatusType.SUBMITTED):
162
173
  try:
163
174
  with open(f"{job.working_dir}/{WORKFLOW_LOG_FILENAME}") as f:
164
175
  job.log = f.read()
@@ -168,13 +179,13 @@ async def view_single_job(
168
179
  return job
169
180
 
170
181
 
171
- @router.patch("/{job_id}/", response_model=JobReadV2)
182
+ @router.patch("/{job_id}/", response_model=JobRead)
172
183
  async def update_job(
173
- job_update: JobUpdateV2,
184
+ job_update: JobUpdate,
174
185
  job_id: int,
175
186
  user: UserOAuth = Depends(current_superuser_act),
176
187
  db: AsyncSession = Depends(get_async_db),
177
- ) -> JobReadV2 | None:
188
+ ) -> JobRead | None:
178
189
  """
179
190
  Change the status of an existing job.
180
191
 
@@ -187,13 +198,13 @@ async def update_job(
187
198
  status_code=status.HTTP_404_NOT_FOUND,
188
199
  detail=f"Job {job_id} not found",
189
200
  )
190
- if job.status != JobStatusTypeV2.SUBMITTED:
201
+ if job.status != JobStatusType.SUBMITTED:
191
202
  raise HTTPException(
192
203
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
193
204
  detail=f"Job {job_id} has status {job.status=} != 'submitted'.",
194
205
  )
195
206
 
196
- if job_update.status != JobStatusTypeV2.FAILED:
207
+ if job_update.status != JobStatusType.FAILED:
197
208
  raise HTTPException(
198
209
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
199
210
  detail=f"Cannot set job status to {job_update.status}",
@@ -206,7 +217,7 @@ async def update_job(
206
217
  job,
207
218
  "log",
208
219
  f"{job.log or ''}\nThis job was manually marked as "
209
- f"'{JobStatusTypeV2.FAILED}' by an admin ({timestamp.isoformat()}).",
220
+ f"'{JobStatusType.FAILED}' by an admin ({timestamp.isoformat()}).",
210
221
  )
211
222
 
212
223
  res = await db.execute(
@@ -6,8 +6,6 @@ from fastapi import status
6
6
  from sqlmodel import func
7
7
  from sqlmodel import select
8
8
 
9
- from ._aux_functions import _check_profile_name
10
- from ._aux_functions import _get_profile_or_404
11
9
  from fractal_server.app.db import AsyncSession
12
10
  from fractal_server.app.db import get_async_db
13
11
  from fractal_server.app.models import Profile
@@ -16,6 +14,9 @@ from fractal_server.app.routes.auth import current_superuser_act
16
14
  from fractal_server.app.schemas.v2 import ProfileCreate
17
15
  from fractal_server.app.schemas.v2 import ProfileRead
18
16
 
17
+ from ._aux_functions import _check_profile_name
18
+ from ._aux_functions import _get_profile_or_404
19
+
19
20
  router = APIRouter()
20
21
 
21
22
 
@@ -6,9 +6,6 @@ from fastapi import status
6
6
  from sqlalchemy.exc import IntegrityError
7
7
  from sqlmodel import select
8
8
 
9
- from ._aux_functions import _check_resource_name
10
- from ._aux_functions import _get_resource_or_404
11
- from .profile import _check_profile_name
12
9
  from fractal_server.app.db import AsyncSession
13
10
  from fractal_server.app.db import get_async_db
14
11
  from fractal_server.app.models import UserOAuth
@@ -22,6 +19,10 @@ from fractal_server.app.schemas.v2 import ResourceRead
22
19
  from fractal_server.config import get_settings
23
20
  from fractal_server.syringe import Inject
24
21
 
22
+ from ._aux_functions import _check_resource_name
23
+ from ._aux_functions import _get_resource_or_404
24
+ from .profile import _check_profile_name
25
+
25
26
  router = APIRouter()
26
27
 
27
28
 
@@ -47,8 +48,7 @@ def _check_type_match_or_422(new_resource: ResourceCreate) -> None:
47
48
  raise HTTPException(
48
49
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
49
50
  detail=(
50
- f"{settings.FRACTAL_RUNNER_BACKEND=} != "
51
- f"{new_resource.type=}"
51
+ f"{settings.FRACTAL_RUNNER_BACKEND=} != {new_resource.type=}"
52
52
  ),
53
53
  )
54
54
 
@@ -0,0 +1,103 @@
1
+ from fastapi import APIRouter
2
+ from fastapi import Depends
3
+ from sqlalchemy import func
4
+ from sqlmodel import select
5
+
6
+ from fractal_server.app.db import AsyncSession
7
+ from fractal_server.app.db import get_async_db
8
+ from fractal_server.app.models import LinkUserProjectV2
9
+ from fractal_server.app.models import UserOAuth
10
+ from fractal_server.app.models.v2 import ProjectV2
11
+ from fractal_server.app.routes.auth import current_superuser_act
12
+ from fractal_server.app.routes.pagination import PaginationRequest
13
+ from fractal_server.app.routes.pagination import PaginationResponse
14
+ from fractal_server.app.routes.pagination import get_pagination_params
15
+ from fractal_server.app.schemas.v2 import LinkUserProjectRead
16
+
17
+ router = APIRouter()
18
+
19
+
20
+ @router.get("/", response_model=PaginationResponse[LinkUserProjectRead])
21
+ async def view_link_user_project(
22
+ # User info
23
+ user_id: int | None = None,
24
+ # Project info
25
+ project_id: int | None = None,
26
+ project_name: str | None = None,
27
+ # Permissions
28
+ is_owner: bool | None = None,
29
+ is_verified: bool | None = None,
30
+ # -----
31
+ pagination: PaginationRequest = Depends(get_pagination_params),
32
+ superuser: UserOAuth = Depends(current_superuser_act),
33
+ db: AsyncSession = Depends(get_async_db),
34
+ ) -> PaginationResponse[LinkUserProjectRead]:
35
+ page = pagination.page
36
+ page_size = pagination.page_size
37
+
38
+ stm = (
39
+ select(
40
+ LinkUserProjectV2,
41
+ UserOAuth.email,
42
+ ProjectV2.name,
43
+ )
44
+ .join(UserOAuth, UserOAuth.id == LinkUserProjectV2.user_id)
45
+ .join(ProjectV2, ProjectV2.id == LinkUserProjectV2.project_id)
46
+ .order_by(UserOAuth.email, ProjectV2.name)
47
+ )
48
+ stm_count = (
49
+ select(func.count())
50
+ .select_from(LinkUserProjectV2)
51
+ .join(UserOAuth, UserOAuth.id == LinkUserProjectV2.user_id)
52
+ .join(ProjectV2, ProjectV2.id == LinkUserProjectV2.project_id)
53
+ )
54
+
55
+ if project_id is not None:
56
+ stm = stm.where(LinkUserProjectV2.project_id == project_id)
57
+ stm_count = stm_count.where(LinkUserProjectV2.project_id == project_id)
58
+ if project_name is not None:
59
+ stm = stm.where(ProjectV2.name.icontains(project_name))
60
+ stm_count = stm_count.where(ProjectV2.name.icontains(project_name))
61
+ if user_id is not None:
62
+ stm = stm.where(LinkUserProjectV2.user_id == user_id)
63
+ stm_count = stm_count.where(LinkUserProjectV2.user_id == user_id)
64
+ if is_owner is not None:
65
+ stm = stm.where(LinkUserProjectV2.is_owner == is_owner)
66
+ stm_count = stm_count.where(LinkUserProjectV2.is_owner == is_owner)
67
+ if is_verified is not None:
68
+ stm = stm.where(LinkUserProjectV2.is_verified == is_verified)
69
+ stm_count = stm_count.where(
70
+ LinkUserProjectV2.is_verified == is_verified
71
+ )
72
+
73
+ res_total_count = await db.execute(stm_count)
74
+
75
+ total_count = res_total_count.scalar()
76
+ if page_size is None:
77
+ page_size = total_count
78
+ else:
79
+ stm = stm.offset((page - 1) * page_size).limit(page_size)
80
+
81
+ res = await db.execute(stm)
82
+ items = res.all()
83
+
84
+ return PaginationResponse[LinkUserProjectRead](
85
+ total_count=total_count,
86
+ page_size=page_size,
87
+ current_page=page,
88
+ items=[
89
+ dict(
90
+ # User info
91
+ user_id=linkuserproject.user_id,
92
+ user_email=user_email,
93
+ # Project info
94
+ project_id=linkuserproject.project_id,
95
+ project_name=project_name,
96
+ # Permissions
97
+ is_verified=linkuserproject.is_verified,
98
+ is_owner=linkuserproject.is_owner,
99
+ permissions=linkuserproject.permissions,
100
+ )
101
+ for linkuserproject, user_email, project_name in items
102
+ ],
103
+ )
@@ -8,21 +8,22 @@ from sqlmodel import select
8
8
 
9
9
  from fractal_server.app.db import AsyncSession
10
10
  from fractal_server.app.db import get_async_db
11
+ from fractal_server.app.models import LinkUserProjectV2
11
12
  from fractal_server.app.models import TaskGroupV2
12
13
  from fractal_server.app.models import UserOAuth
13
14
  from fractal_server.app.models.v2 import TaskV2
14
15
  from fractal_server.app.models.v2 import WorkflowTaskV2
15
16
  from fractal_server.app.models.v2 import WorkflowV2
16
17
  from fractal_server.app.routes.auth import current_superuser_act
17
- from fractal_server.app.routes.pagination import get_pagination_params
18
18
  from fractal_server.app.routes.pagination import PaginationRequest
19
19
  from fractal_server.app.routes.pagination import PaginationResponse
20
+ from fractal_server.app.routes.pagination import get_pagination_params
20
21
  from fractal_server.app.schemas.v2.task import TaskType
21
22
 
22
23
  router = APIRouter()
23
24
 
24
25
 
25
- class TaskV2Minimal(BaseModel):
26
+ class TaskMinimal(BaseModel):
26
27
  id: int
27
28
  name: str
28
29
  type: str
@@ -38,7 +39,7 @@ class ProjectUser(BaseModel):
38
39
  email: EmailStr
39
40
 
40
41
 
41
- class TaskV2Relationship(BaseModel):
42
+ class TaskRelationship(BaseModel):
42
43
  workflow_id: int
43
44
  workflow_name: str
44
45
  project_id: int
@@ -46,12 +47,12 @@ class TaskV2Relationship(BaseModel):
46
47
  project_users: list[ProjectUser] = Field(default_factory=list)
47
48
 
48
49
 
49
- class TaskV2Info(BaseModel):
50
- task: TaskV2Minimal
51
- relationships: list[TaskV2Relationship]
50
+ class TaskInfo(BaseModel):
51
+ task: TaskMinimal
52
+ relationships: list[TaskRelationship]
52
53
 
53
54
 
54
- @router.get("/", response_model=PaginationResponse[TaskV2Info])
55
+ @router.get("/", response_model=PaginationResponse[TaskInfo])
55
56
  async def query_tasks(
56
57
  id: int | None = None,
57
58
  source: str | None = None,
@@ -65,7 +66,7 @@ async def query_tasks(
65
66
  pagination: PaginationRequest = Depends(get_pagination_params),
66
67
  user: UserOAuth = Depends(current_superuser_act),
67
68
  db: AsyncSession = Depends(get_async_db),
68
- ) -> PaginationResponse[TaskV2Info]:
69
+ ) -> PaginationResponse[TaskInfo]:
69
70
  """
70
71
  Query `TaskV2` and get information about related workflows and projects.
71
72
  """
@@ -106,16 +107,12 @@ async def query_tasks(
106
107
  stm = stm.where(TaskV2.authors.icontains(author))
107
108
  stm_count = stm_count.where(TaskV2.authors.icontains(author))
108
109
  if resource_id is not None:
109
- stm = (
110
- stm.join(TaskGroupV2)
111
- .where(TaskGroupV2.id == TaskV2.taskgroupv2_id)
112
- .where(TaskGroupV2.resource_id == resource_id)
113
- )
114
- stm_count = (
115
- stm_count.join(TaskGroupV2)
116
- .where(TaskGroupV2.id == TaskV2.taskgroupv2_id)
117
- .where(TaskGroupV2.resource_id == resource_id)
118
- )
110
+ stm = stm.join(
111
+ TaskGroupV2, TaskGroupV2.id == TaskV2.taskgroupv2_id
112
+ ).where(TaskGroupV2.resource_id == resource_id)
113
+ stm_count = stm_count.join(
114
+ TaskGroupV2, TaskGroupV2.id == TaskV2.taskgroupv2_id
115
+ ).where(TaskGroupV2.resource_id == resource_id)
119
116
 
120
117
  # Find total number of elements
121
118
  res_total_count = await db.execute(stm_count)
@@ -133,13 +130,31 @@ async def query_tasks(
133
130
  for task in task_list:
134
131
  stm = (
135
132
  select(WorkflowV2)
136
- .join(WorkflowTaskV2)
137
- .where(WorkflowTaskV2.workflow_id == WorkflowV2.id)
133
+ .join(
134
+ WorkflowTaskV2,
135
+ WorkflowTaskV2.workflow_id == WorkflowV2.id,
136
+ )
138
137
  .where(WorkflowTaskV2.task_id == task.id)
139
138
  )
140
139
  res = await db.execute(stm)
141
140
  wf_list = res.scalars().all()
142
141
 
142
+ project_users = {}
143
+ for project_id in set([workflow.project_id for workflow in wf_list]):
144
+ res = await db.execute(
145
+ select(UserOAuth.id, UserOAuth.email)
146
+ .join(
147
+ LinkUserProjectV2,
148
+ LinkUserProjectV2.user_id == UserOAuth.id,
149
+ )
150
+ .where(LinkUserProjectV2.project_id == project_id)
151
+ .where(LinkUserProjectV2.is_owner.is_(True))
152
+ )
153
+ project_users[project_id] = [
154
+ ProjectUser(id=p_user[0], email=p_user[1])
155
+ for p_user in res.all()
156
+ ]
157
+
143
158
  task_info_list.append(
144
159
  dict(
145
160
  task=task.model_dump(),
@@ -149,17 +164,13 @@ async def query_tasks(
149
164
  workflow_name=workflow.name,
150
165
  project_id=workflow.project.id,
151
166
  project_name=workflow.project.name,
152
- project_users=[
153
- dict(id=user.id, email=user.email)
154
- for user in workflow.project.user_list
155
- ],
167
+ project_users=project_users[workflow.project_id],
156
168
  )
157
169
  for workflow in wf_list
158
170
  ],
159
171
  )
160
172
  )
161
-
162
- return PaginationResponse[TaskV2Info](
173
+ return dict(
163
174
  total_count=total_count,
164
175
  page_size=page_size,
165
176
  current_page=page,
@@ -5,6 +5,7 @@ from fastapi import status
5
5
  from pydantic.types import AwareDatetime
6
6
  from sqlalchemy.sql.operators import is_
7
7
  from sqlalchemy.sql.operators import is_not
8
+ from sqlmodel import func
8
9
  from sqlmodel import select
9
10
 
10
11
  from fractal_server.app.db import AsyncSession
@@ -16,53 +17,94 @@ from fractal_server.app.routes.auth import current_superuser_act
16
17
  from fractal_server.app.routes.auth._aux_auth import (
17
18
  _verify_user_belongs_to_group,
18
19
  )
19
- from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
20
- from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
21
- from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
20
+ from fractal_server.app.routes.pagination import PaginationRequest
21
+ from fractal_server.app.routes.pagination import PaginationResponse
22
+ from fractal_server.app.routes.pagination import get_pagination_params
23
+ from fractal_server.app.schemas.v2 import TaskGroupActivityAction
24
+ from fractal_server.app.schemas.v2 import TaskGroupActivityRead
25
+ from fractal_server.app.schemas.v2 import TaskGroupActivityStatus
26
+ from fractal_server.app.schemas.v2 import TaskGroupOriginEnum
22
27
  from fractal_server.app.schemas.v2 import TaskGroupReadSuperuser
23
- from fractal_server.app.schemas.v2 import TaskGroupUpdateV2
24
- from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
28
+ from fractal_server.app.schemas.v2 import TaskGroupUpdate
25
29
  from fractal_server.logger import set_logger
26
30
 
27
-
28
31
  router = APIRouter()
29
32
 
30
33
  logger = set_logger(__name__)
31
34
 
32
35
 
33
- @router.get("/activity/", response_model=list[TaskGroupActivityV2Read])
36
+ @router.get(
37
+ "/activity/", response_model=PaginationResponse[TaskGroupActivityRead]
38
+ )
34
39
  async def get_task_group_activity_list(
35
40
  task_group_activity_id: int | None = None,
36
41
  user_id: int | None = None,
37
42
  taskgroupv2_id: int | None = None,
38
43
  pkg_name: str | None = None,
39
- status: TaskGroupActivityStatusV2 | None = None,
40
- action: TaskGroupActivityActionV2 | None = None,
44
+ status: TaskGroupActivityStatus | None = None,
45
+ action: TaskGroupActivityAction | None = None,
41
46
  timestamp_started_min: AwareDatetime | None = None,
47
+ pagination: PaginationRequest = Depends(get_pagination_params),
42
48
  superuser: UserOAuth = Depends(current_superuser_act),
43
49
  db: AsyncSession = Depends(get_async_db),
44
- ) -> list[TaskGroupActivityV2Read]:
45
- stm = select(TaskGroupActivityV2)
50
+ ) -> PaginationResponse[TaskGroupActivityRead]:
51
+ # Assign pagination parameters
52
+ page = pagination.page
53
+ page_size = pagination.page_size
54
+
55
+ stm = select(TaskGroupActivityV2).order_by(
56
+ TaskGroupActivityV2.timestamp_started.desc()
57
+ )
58
+ stm_count = select(func.count(TaskGroupActivityV2.id))
46
59
  if task_group_activity_id is not None:
47
60
  stm = stm.where(TaskGroupActivityV2.id == task_group_activity_id)
61
+ stm_count = stm_count.where(
62
+ TaskGroupActivityV2.id == task_group_activity_id
63
+ )
48
64
  if user_id:
49
65
  stm = stm.where(TaskGroupActivityV2.user_id == user_id)
66
+ stm_count = stm_count.where(TaskGroupActivityV2.user_id == user_id)
50
67
  if taskgroupv2_id:
51
68
  stm = stm.where(TaskGroupActivityV2.taskgroupv2_id == taskgroupv2_id)
69
+ stm_count = stm_count.where(
70
+ TaskGroupActivityV2.taskgroupv2_id == taskgroupv2_id
71
+ )
52
72
  if pkg_name:
53
73
  stm = stm.where(TaskGroupActivityV2.pkg_name.icontains(pkg_name))
74
+ stm_count = stm_count.where(
75
+ TaskGroupActivityV2.pkg_name.icontains(pkg_name)
76
+ )
54
77
  if status:
55
78
  stm = stm.where(TaskGroupActivityV2.status == status)
79
+ stm_count = stm_count.where(TaskGroupActivityV2.status == status)
56
80
  if action:
57
81
  stm = stm.where(TaskGroupActivityV2.action == action)
82
+ stm_count = stm_count.where(TaskGroupActivityV2.action == action)
58
83
  if timestamp_started_min is not None:
59
84
  stm = stm.where(
60
85
  TaskGroupActivityV2.timestamp_started >= timestamp_started_min
61
86
  )
87
+ stm_count = stm_count.where(
88
+ TaskGroupActivityV2.timestamp_started >= timestamp_started_min
89
+ )
90
+
91
+ # Find total number of elements
92
+ res_total_count = await db.execute(stm_count)
93
+ total_count = res_total_count.scalar()
94
+ if page_size is None:
95
+ page_size = total_count
96
+ else:
97
+ stm = stm.offset((page - 1) * page_size).limit(page_size)
62
98
 
63
99
  res = await db.execute(stm)
64
100
  activities = res.scalars().all()
65
- return activities
101
+
102
+ return dict(
103
+ total_count=total_count,
104
+ page_size=page_size,
105
+ current_page=page,
106
+ items=activities,
107
+ )
66
108
 
67
109
 
68
110
  @router.get("/{task_group_id}/", response_model=TaskGroupReadSuperuser)
@@ -80,21 +122,27 @@ async def query_task_group(
80
122
  return task_group
81
123
 
82
124
 
83
- @router.get("/", response_model=list[TaskGroupReadSuperuser])
125
+ @router.get("/", response_model=PaginationResponse[TaskGroupReadSuperuser])
84
126
  async def query_task_group_list(
85
127
  user_id: int | None = None,
86
128
  user_group_id: int | None = None,
87
129
  private: bool | None = None,
88
130
  active: bool | None = None,
89
131
  pkg_name: str | None = None,
90
- origin: TaskGroupV2OriginEnum | None = None,
132
+ origin: TaskGroupOriginEnum | None = None,
91
133
  timestamp_last_used_min: AwareDatetime | None = None,
92
134
  timestamp_last_used_max: AwareDatetime | None = None,
93
135
  resource_id: int | None = None,
136
+ pagination: PaginationRequest = Depends(get_pagination_params),
94
137
  user: UserOAuth = Depends(current_superuser_act),
95
138
  db: AsyncSession = Depends(get_async_db),
96
- ) -> list[TaskGroupReadSuperuser]:
139
+ ) -> PaginationResponse[TaskGroupReadSuperuser]:
140
+ # Assign pagination parameters
141
+ page = pagination.page
142
+ page_size = pagination.page_size
143
+
97
144
  stm = select(TaskGroupV2)
145
+ stm_count = select(func.count(TaskGroupV2.id))
98
146
 
99
147
  if user_group_id is not None and private is True:
100
148
  raise HTTPException(
@@ -106,43 +154,72 @@ async def query_task_group_list(
106
154
  )
107
155
  if user_id is not None:
108
156
  stm = stm.where(TaskGroupV2.user_id == user_id)
157
+ stm_count = stm_count.where(TaskGroupV2.user_id == user_id)
109
158
  if user_group_id is not None:
110
159
  stm = stm.where(TaskGroupV2.user_group_id == user_group_id)
160
+ stm_count = stm_count.where(TaskGroupV2.user_group_id == user_group_id)
111
161
  if private is not None:
112
162
  if private is True:
113
163
  stm = stm.where(is_(TaskGroupV2.user_group_id, None))
164
+ stm_count = stm_count.where(is_(TaskGroupV2.user_group_id, None))
114
165
  else:
115
166
  stm = stm.where(is_not(TaskGroupV2.user_group_id, None))
167
+ stm_count = stm_count.where(is_not(TaskGroupV2.user_group_id, None))
116
168
  if active is not None:
117
169
  if active is True:
118
170
  stm = stm.where(is_(TaskGroupV2.active, True))
171
+ stm_count = stm_count.where(is_(TaskGroupV2.active, True))
119
172
  else:
120
173
  stm = stm.where(is_(TaskGroupV2.active, False))
174
+ stm_count = stm_count.where(is_(TaskGroupV2.active, False))
121
175
  if origin is not None:
122
176
  stm = stm.where(TaskGroupV2.origin == origin)
177
+ stm_count = stm_count.where(TaskGroupV2.origin == origin)
123
178
  if pkg_name is not None:
124
179
  stm = stm.where(TaskGroupV2.pkg_name.icontains(pkg_name))
180
+ stm_count = stm_count.where(TaskGroupV2.pkg_name.icontains(pkg_name))
125
181
  if timestamp_last_used_min is not None:
126
182
  stm = stm.where(
127
183
  TaskGroupV2.timestamp_last_used >= timestamp_last_used_min
128
184
  )
185
+ stm_count = stm_count.where(
186
+ TaskGroupV2.timestamp_last_used >= timestamp_last_used_min
187
+ )
129
188
  if timestamp_last_used_max is not None:
130
189
  stm = stm.where(
131
190
  TaskGroupV2.timestamp_last_used <= timestamp_last_used_max
132
191
  )
192
+ stm_count = stm_count.where(
193
+ TaskGroupV2.timestamp_last_used <= timestamp_last_used_max
194
+ )
133
195
  if resource_id is not None:
134
196
  stm = stm.where(TaskGroupV2.resource_id == resource_id)
197
+ stm_count = stm_count.where(TaskGroupV2.resource_id == resource_id)
198
+
199
+ # Find total number of elements
200
+ res_total_count = await db.execute(stm_count)
201
+ total_count = res_total_count.scalar()
202
+ if page_size is None:
203
+ page_size = total_count
204
+ else:
205
+ stm = stm.offset((page - 1) * page_size).limit(page_size)
135
206
 
136
207
  stm = stm.order_by(TaskGroupV2.id)
137
208
  res = await db.execute(stm)
138
209
  task_groups_list = res.scalars().all()
139
- return task_groups_list
210
+
211
+ return dict(
212
+ total_count=total_count,
213
+ page_size=page_size,
214
+ current_page=page,
215
+ items=task_groups_list,
216
+ )
140
217
 
141
218
 
142
219
  @router.patch("/{task_group_id}/", response_model=TaskGroupReadSuperuser)
143
220
  async def patch_task_group(
144
221
  task_group_id: int,
145
- task_group_update: TaskGroupUpdateV2,
222
+ task_group_update: TaskGroupUpdate,
146
223
  user: UserOAuth = Depends(current_superuser_act),
147
224
  db: AsyncSession = Depends(get_async_db),
148
225
  ) -> list[TaskGroupReadSuperuser]: