fractal-server 2.18.5__py3-none-any.whl → 2.19.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 (64) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/db/__init__.py +1 -7
  3. fractal_server/app/models/security.py +16 -0
  4. fractal_server/app/models/v2/dataset.py +0 -4
  5. fractal_server/app/models/v2/job.py +4 -0
  6. fractal_server/app/models/v2/task.py +0 -1
  7. fractal_server/app/models/v2/task_group.py +4 -0
  8. fractal_server/app/models/v2/workflow.py +2 -0
  9. fractal_server/app/models/v2/workflowtask.py +3 -0
  10. fractal_server/app/routes/admin/v2/job.py +0 -2
  11. fractal_server/app/routes/admin/v2/sharing.py +47 -0
  12. fractal_server/app/routes/admin/v2/task.py +0 -5
  13. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +6 -0
  14. fractal_server/app/routes/api/__init__.py +4 -52
  15. fractal_server/app/routes/api/alive.py +13 -0
  16. fractal_server/app/routes/api/settings.py +44 -0
  17. fractal_server/app/routes/api/v2/__init__.py +0 -2
  18. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +1 -20
  19. fractal_server/app/routes/api/v2/dataset.py +9 -8
  20. fractal_server/app/routes/api/v2/history.py +8 -8
  21. fractal_server/app/routes/api/v2/images.py +6 -6
  22. fractal_server/app/routes/api/v2/job.py +10 -12
  23. fractal_server/app/routes/api/v2/pre_submission_checks.py +3 -3
  24. fractal_server/app/routes/api/v2/project.py +7 -9
  25. fractal_server/app/routes/api/v2/sharing.py +17 -9
  26. fractal_server/app/routes/api/v2/submit.py +5 -3
  27. fractal_server/app/routes/api/v2/task.py +7 -9
  28. fractal_server/app/routes/api/v2/task_collection.py +4 -2
  29. fractal_server/app/routes/api/v2/task_collection_custom.py +2 -2
  30. fractal_server/app/routes/api/v2/task_collection_pixi.py +4 -2
  31. fractal_server/app/routes/api/v2/task_group.py +10 -30
  32. fractal_server/app/routes/api/v2/task_group_lifecycle.py +10 -4
  33. fractal_server/app/routes/api/v2/task_version_update.py +4 -3
  34. fractal_server/app/routes/api/v2/workflow.py +10 -11
  35. fractal_server/app/routes/api/v2/workflow_import.py +14 -45
  36. fractal_server/app/routes/api/v2/workflowtask.py +7 -12
  37. fractal_server/app/routes/auth/__init__.py +18 -1
  38. fractal_server/app/routes/auth/current_user.py +8 -0
  39. fractal_server/app/routes/auth/oauth.py +3 -1
  40. fractal_server/app/routes/auth/users.py +11 -0
  41. fractal_server/app/routes/aux/_versions.py +42 -0
  42. fractal_server/app/schemas/user.py +7 -0
  43. fractal_server/app/schemas/v2/__init__.py +0 -1
  44. fractal_server/app/schemas/v2/dumps.py +0 -1
  45. fractal_server/app/schemas/v2/task.py +0 -5
  46. fractal_server/app/schemas/v2/workflow.py +2 -0
  47. fractal_server/app/schemas/v2/workflowtask.py +6 -2
  48. fractal_server/app/security/__init__.py +8 -3
  49. fractal_server/migrations/versions/18a26fcdea5d_drop_dataset_history.py +41 -0
  50. fractal_server/migrations/versions/1bf8785755f9_add_description_to_workflow_and_.py +53 -0
  51. fractal_server/migrations/versions/5fb08bf05b14_drop_taskv2_source.py +36 -0
  52. fractal_server/migrations/versions/cfd13f7954e7_add_fractal_server_version_to_jobv2_and_.py +52 -0
  53. fractal_server/migrations/versions/e53dc51fdf93_add_useroauth_is_guest.py +36 -0
  54. fractal_server/runner/executors/local/runner.py +2 -0
  55. fractal_server/runner/executors/slurm_ssh/runner.py +5 -0
  56. fractal_server/runner/executors/slurm_sudo/runner.py +5 -0
  57. fractal_server/runner/v2/runner.py +0 -1
  58. fractal_server/runner/v2/submit_workflow.py +0 -3
  59. {fractal_server-2.18.5.dist-info → fractal_server-2.19.0.dist-info}/METADATA +3 -3
  60. {fractal_server-2.18.5.dist-info → fractal_server-2.19.0.dist-info}/RECORD +63 -56
  61. {fractal_server-2.18.5.dist-info → fractal_server-2.19.0.dist-info}/WHEEL +1 -1
  62. fractal_server/app/routes/api/v2/status_legacy.py +0 -156
  63. {fractal_server-2.18.5.dist-info → fractal_server-2.19.0.dist-info}/entry_points.txt +0 -0
  64. {fractal_server-2.18.5.dist-info → fractal_server-2.19.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,7 +15,8 @@ from fractal_server.app.db import get_async_db
15
15
  from fractal_server.app.models import UserOAuth
16
16
  from fractal_server.app.models.v2 import JobV2
17
17
  from fractal_server.app.models.v2 import LinkUserProjectV2
18
- from fractal_server.app.routes.auth import current_user_act_ver_prof
18
+ from fractal_server.app.routes.auth import get_api_guest
19
+ from fractal_server.app.routes.auth import get_api_user
19
20
  from fractal_server.app.routes.aux._job import _write_shutdown_file
20
21
  from fractal_server.app.routes.aux._runner import _check_shutdown_is_supported
21
22
  from fractal_server.app.schemas.v2 import JobRead
@@ -41,12 +42,12 @@ router = APIRouter()
41
42
 
42
43
  @router.get("/job/", response_model=list[JobRead])
43
44
  async def get_user_jobs(
44
- user: UserOAuth = Depends(current_user_act_ver_prof),
45
+ user: UserOAuth = Depends(get_api_guest),
45
46
  log: bool = True,
46
47
  db: AsyncSession = Depends(get_async_db),
47
48
  ) -> list[JobRead]:
48
49
  """
49
- Returns all the jobs of the current user
50
+ Returns all the jobs from projects linked to the current user
50
51
  """
51
52
  stm = (
52
53
  select(JobV2)
@@ -59,7 +60,6 @@ async def get_user_jobs(
59
60
  )
60
61
  res = await db.execute(stm)
61
62
  job_list = res.scalars().all()
62
- await db.close()
63
63
  if not log:
64
64
  for job in job_list:
65
65
  setattr(job, "log", None)
@@ -74,7 +74,7 @@ async def get_user_jobs(
74
74
  async def get_workflow_jobs(
75
75
  project_id: int,
76
76
  workflow_id: int,
77
- user: UserOAuth = Depends(current_user_act_ver_prof),
77
+ user: UserOAuth = Depends(get_api_guest),
78
78
  db: AsyncSession = Depends(get_async_db),
79
79
  ) -> list[JobRead] | None:
80
80
  """
@@ -101,7 +101,7 @@ async def get_latest_job(
101
101
  project_id: int,
102
102
  workflow_id: int,
103
103
  dataset_id: int,
104
- user: UserOAuth = Depends(current_user_act_ver_prof),
104
+ user: UserOAuth = Depends(get_api_guest),
105
105
  db: AsyncSession = Depends(get_async_db),
106
106
  ) -> JobRead:
107
107
  await _get_workflow_check_access(
@@ -137,7 +137,7 @@ async def read_job(
137
137
  project_id: int,
138
138
  job_id: int,
139
139
  show_tmp_logs: bool = False,
140
- user: UserOAuth = Depends(current_user_act_ver_prof),
140
+ user: UserOAuth = Depends(get_api_guest),
141
141
  db: AsyncSession = Depends(get_async_db),
142
142
  ) -> JobRead | None:
143
143
  """
@@ -152,7 +152,6 @@ async def read_job(
152
152
  db=db,
153
153
  )
154
154
  job = output["job"]
155
- await db.close()
156
155
 
157
156
  if show_tmp_logs and (job.status == JobStatusType.SUBMITTED):
158
157
  try:
@@ -171,7 +170,7 @@ async def read_job(
171
170
  async def download_job_logs(
172
171
  project_id: int,
173
172
  job_id: int,
174
- user: UserOAuth = Depends(current_user_act_ver_prof),
173
+ user: UserOAuth = Depends(get_api_guest),
175
174
  db: AsyncSession = Depends(get_async_db),
176
175
  ) -> StreamingResponse:
177
176
  """
@@ -202,7 +201,7 @@ async def download_job_logs(
202
201
  )
203
202
  async def get_job_list(
204
203
  project_id: int,
205
- user: UserOAuth = Depends(current_user_act_ver_prof),
204
+ user: UserOAuth = Depends(get_api_guest),
206
205
  log: bool = True,
207
206
  db: AsyncSession = Depends(get_async_db),
208
207
  ) -> list[JobRead] | None:
@@ -222,7 +221,6 @@ async def get_job_list(
222
221
  .order_by(JobV2.start_timestamp.desc())
223
222
  )
224
223
  job_list = res.scalars().all()
225
- await db.close()
226
224
  if not log:
227
225
  for job in job_list:
228
226
  setattr(job, "log", None)
@@ -237,7 +235,7 @@ async def get_job_list(
237
235
  async def stop_job(
238
236
  project_id: int,
239
237
  job_id: int,
240
- user: UserOAuth = Depends(current_user_act_ver_prof),
238
+ user: UserOAuth = Depends(get_api_user),
241
239
  db: AsyncSession = Depends(get_async_db),
242
240
  ) -> Response:
243
241
  """
@@ -8,7 +8,7 @@ from pydantic import Field
8
8
  from fractal_server.app.db import AsyncSession
9
9
  from fractal_server.app.db import get_async_db
10
10
  from fractal_server.app.models import UserOAuth
11
- from fractal_server.app.routes.auth import current_user_act_ver_prof
11
+ from fractal_server.app.routes.auth import get_api_guest
12
12
  from fractal_server.app.schemas.v2 import HistoryUnitStatus
13
13
  from fractal_server.app.schemas.v2 import TaskType
14
14
  from fractal_server.app.schemas.v2.sharing import ProjectPermissions
@@ -34,7 +34,7 @@ async def verify_unique_types(
34
34
  dataset_id: int,
35
35
  workflowtask_id: int,
36
36
  query: ImageQuery | None = None,
37
- user: UserOAuth = Depends(current_user_act_ver_prof),
37
+ user: UserOAuth = Depends(get_api_guest),
38
38
  db: AsyncSession = Depends(get_async_db),
39
39
  ) -> list[str]:
40
40
  # Get dataset
@@ -99,7 +99,7 @@ async def check_non_processed_images(
99
99
  workflow_id: int,
100
100
  workflowtask_id: int,
101
101
  filters: NonProcessedImagesPayload,
102
- user: UserOAuth = Depends(current_user_act_ver_prof),
102
+ user: UserOAuth = Depends(get_api_guest),
103
103
  db: AsyncSession = Depends(get_async_db),
104
104
  ) -> JSONResponse:
105
105
  db_workflow_task, db_workflow = await _get_workflow_task_check_access(
@@ -11,7 +11,8 @@ from fractal_server.app.models import UserOAuth
11
11
  from fractal_server.app.models.v2 import JobV2
12
12
  from fractal_server.app.models.v2 import LinkUserProjectV2
13
13
  from fractal_server.app.models.v2 import ProjectV2
14
- from fractal_server.app.routes.auth import current_user_act_ver_prof
14
+ from fractal_server.app.routes.auth import get_api_guest
15
+ from fractal_server.app.routes.auth import get_api_user
15
16
  from fractal_server.app.routes.aux.validate_user_profile import (
16
17
  validate_user_profile,
17
18
  )
@@ -32,7 +33,7 @@ router = APIRouter()
32
33
  @router.get("/project/", response_model=list[ProjectRead])
33
34
  async def get_list_project(
34
35
  is_owner: bool = True,
35
- user: UserOAuth = Depends(current_user_act_ver_prof),
36
+ user: UserOAuth = Depends(get_api_guest),
36
37
  db: AsyncSession = Depends(get_async_db),
37
38
  ) -> list[ProjectV2]:
38
39
  """
@@ -47,14 +48,13 @@ async def get_list_project(
47
48
  )
48
49
  res = await db.execute(stm)
49
50
  project_list = res.scalars().all()
50
- await db.close()
51
51
  return project_list
52
52
 
53
53
 
54
54
  @router.post("/project/", response_model=ProjectRead, status_code=201)
55
55
  async def create_project(
56
56
  project: ProjectCreate,
57
- user: UserOAuth = Depends(current_user_act_ver_prof),
57
+ user: UserOAuth = Depends(get_api_user),
58
58
  db: AsyncSession = Depends(get_async_db),
59
59
  ) -> ProjectRead | None:
60
60
  """
@@ -95,7 +95,7 @@ async def create_project(
95
95
  @router.get("/project/{project_id}/", response_model=ProjectRead)
96
96
  async def read_project(
97
97
  project_id: int,
98
- user: UserOAuth = Depends(current_user_act_ver_prof),
98
+ user: UserOAuth = Depends(get_api_guest),
99
99
  db: AsyncSession = Depends(get_async_db),
100
100
  ) -> ProjectRead | None:
101
101
  """
@@ -107,7 +107,6 @@ async def read_project(
107
107
  required_permissions=ProjectPermissions.READ,
108
108
  db=db,
109
109
  )
110
- await db.close()
111
110
  return project
112
111
 
113
112
 
@@ -115,7 +114,7 @@ async def read_project(
115
114
  async def update_project(
116
115
  project_id: int,
117
116
  project_update: ProjectUpdate,
118
- user: UserOAuth = Depends(current_user_act_ver_prof),
117
+ user: UserOAuth = Depends(get_api_user),
119
118
  db: AsyncSession = Depends(get_async_db),
120
119
  ):
121
120
  project = await _get_project_check_access(
@@ -136,14 +135,13 @@ async def update_project(
136
135
 
137
136
  await db.commit()
138
137
  await db.refresh(project)
139
- await db.close()
140
138
  return project
141
139
 
142
140
 
143
141
  @router.delete("/project/{project_id}/", status_code=204)
144
142
  async def delete_project(
145
143
  project_id: int,
146
- user: UserOAuth = Depends(current_user_act_ver_prof),
144
+ user: UserOAuth = Depends(get_api_user),
147
145
  db: AsyncSession = Depends(get_async_db),
148
146
  ) -> Response:
149
147
  """
@@ -11,7 +11,8 @@ from fractal_server.app.db import get_async_db
11
11
  from fractal_server.app.models import UserOAuth
12
12
  from fractal_server.app.models.v2 import LinkUserProjectV2
13
13
  from fractal_server.app.models.v2 import ProjectV2
14
- from fractal_server.app.routes.auth import current_user_act_ver_prof
14
+ from fractal_server.app.routes.auth import get_api_guest
15
+ from fractal_server.app.routes.auth import get_api_user
15
16
  from fractal_server.app.schemas.v2 import ProjectAccessRead
16
17
  from fractal_server.app.schemas.v2 import ProjectGuestCreate
17
18
  from fractal_server.app.schemas.v2 import ProjectGuestRead
@@ -33,7 +34,7 @@ router = APIRouter()
33
34
  )
34
35
  async def get_project_guests(
35
36
  project_id: int,
36
- owner: UserOAuth = Depends(current_user_act_ver_prof),
37
+ owner: UserOAuth = Depends(get_api_guest),
37
38
  db: AsyncSession = Depends(get_async_db),
38
39
  ) -> list[ProjectGuestRead]:
39
40
  """
@@ -68,7 +69,7 @@ async def invite_guest(
68
69
  project_id: int,
69
70
  email: EmailStr,
70
71
  project_invitation: ProjectGuestCreate,
71
- owner: UserOAuth = Depends(current_user_act_ver_prof),
72
+ owner: UserOAuth = Depends(get_api_user),
72
73
  db: AsyncSession = Depends(get_async_db),
73
74
  ) -> Response:
74
75
  """
@@ -103,7 +104,7 @@ async def patch_guest(
103
104
  project_id: int,
104
105
  email: EmailStr,
105
106
  update: ProjectGuestUpdate,
106
- owner: UserOAuth = Depends(current_user_act_ver_prof),
107
+ owner: UserOAuth = Depends(get_api_user),
107
108
  db: AsyncSession = Depends(get_async_db),
108
109
  ) -> Response:
109
110
  """
@@ -137,7 +138,7 @@ async def patch_guest(
137
138
  async def revoke_guest_access(
138
139
  project_id: int,
139
140
  email: EmailStr,
140
- owner: UserOAuth = Depends(current_user_act_ver_prof),
141
+ owner: UserOAuth = Depends(get_api_user),
141
142
  db: AsyncSession = Depends(get_async_db),
142
143
  ) -> Response:
143
144
  """
@@ -171,13 +172,20 @@ async def revoke_guest_access(
171
172
  response_model=list[ProjectInvitationRead],
172
173
  )
173
174
  async def get_pending_invitations(
174
- user: UserOAuth = Depends(current_user_act_ver_prof),
175
+ user: UserOAuth = Depends(get_api_guest),
175
176
  db: AsyncSession = Depends(get_async_db),
176
177
  ) -> list[ProjectInvitationRead]:
177
178
  """
178
179
  See your current invitations.
179
180
  """
180
181
 
182
+ if user.is_guest:
183
+ # The user's attribute `is_guest` is used to identify guest accounts,
184
+ # i.e. accounts with read only permissions on the API.
185
+ # This is a different concept from a project guest, which is a regular
186
+ # account with which a project has been shared.
187
+ return []
188
+
181
189
  res = await db.execute(
182
190
  select(
183
191
  ProjectV2.id,
@@ -225,7 +233,7 @@ async def get_pending_invitations(
225
233
  )
226
234
  async def get_access_info(
227
235
  project_id: int,
228
- user: UserOAuth = Depends(current_user_act_ver_prof),
236
+ user: UserOAuth = Depends(get_api_guest),
229
237
  db: AsyncSession = Depends(get_async_db),
230
238
  ) -> ProjectAccessRead:
231
239
  """
@@ -272,7 +280,7 @@ async def get_access_info(
272
280
  @router.post("/project/{project_id}/access/accept/", status_code=200)
273
281
  async def accept_project_invitation(
274
282
  project_id: int,
275
- user: UserOAuth = Depends(current_user_act_ver_prof),
283
+ user: UserOAuth = Depends(get_api_user),
276
284
  db: AsyncSession = Depends(get_async_db),
277
285
  ) -> Response:
278
286
  """
@@ -290,7 +298,7 @@ async def accept_project_invitation(
290
298
  @router.delete("/project/{project_id}/access/", status_code=204)
291
299
  async def leave_project(
292
300
  project_id: int,
293
- user: UserOAuth = Depends(current_user_act_ver_prof),
301
+ user: UserOAuth = Depends(get_api_user),
294
302
  db: AsyncSession = Depends(get_async_db),
295
303
  ) -> Response:
296
304
  """
@@ -11,6 +11,7 @@ from fastapi import status
11
11
  from sqlmodel import select
12
12
  from sqlmodel import update
13
13
 
14
+ from fractal_server import __VERSION__
14
15
  from fractal_server.app.db import AsyncSession
15
16
  from fractal_server.app.db import get_async_db
16
17
  from fractal_server.app.models import Profile
@@ -20,7 +21,7 @@ from fractal_server.app.models.v2 import JobV2
20
21
  from fractal_server.app.routes.api.v2._aux_functions_tasks import (
21
22
  _get_task_read_access,
22
23
  )
23
- from fractal_server.app.routes.auth import current_user_act_ver_prof
24
+ from fractal_server.app.routes.auth import get_api_user
24
25
  from fractal_server.app.routes.aux.validate_user_profile import (
25
26
  validate_user_profile,
26
27
  )
@@ -59,7 +60,7 @@ async def submit_job(
59
60
  job_create: JobCreate,
60
61
  background_tasks: BackgroundTasks,
61
62
  request: Request,
62
- user: UserOAuth = Depends(current_user_act_ver_prof),
63
+ user: UserOAuth = Depends(get_api_user),
63
64
  db: AsyncSession = Depends(get_async_db),
64
65
  ) -> JobRead | None:
65
66
  # Remove non-submitted Jobs from the app state when the list grows
@@ -216,11 +217,12 @@ async def submit_job(
216
217
  dataset.model_dump_json(exclude={"images", "history"})
217
218
  ),
218
219
  workflow_dump=json.loads(
219
- workflow.model_dump_json(exclude={"task_list"})
220
+ workflow.model_dump_json(exclude={"task_list", "description"})
220
221
  ),
221
222
  project_dump=json.loads(
222
223
  project.model_dump_json(exclude={"resource_id"})
223
224
  ),
225
+ fractal_server_version=__VERSION__,
224
226
  **job_create.model_dump(),
225
227
  )
226
228
 
@@ -24,7 +24,8 @@ from fractal_server.app.models import LinkUserGroup
24
24
  from fractal_server.app.models import UserOAuth
25
25
  from fractal_server.app.models.v2 import TaskGroupV2
26
26
  from fractal_server.app.models.v2 import TaskV2
27
- from fractal_server.app.routes.auth import current_user_act_ver_prof
27
+ from fractal_server.app.routes.auth import get_api_guest
28
+ from fractal_server.app.routes.auth import get_api_user
28
29
  from fractal_server.app.schemas.v2 import TaskCreate
29
30
  from fractal_server.app.schemas.v2 import TaskGroupOriginEnum
30
31
  from fractal_server.app.schemas.v2 import TaskRead
@@ -43,7 +44,7 @@ async def get_list_task(
43
44
  category: str | None = None,
44
45
  modality: str | None = None,
45
46
  author: str | None = None,
46
- user: UserOAuth = Depends(current_user_act_ver_prof),
47
+ user: UserOAuth = Depends(get_api_guest),
47
48
  db: AsyncSession = Depends(get_async_db),
48
49
  ) -> list[TaskRead]:
49
50
  """
@@ -77,7 +78,6 @@ async def get_list_task(
77
78
  stm = stm.order_by(TaskV2.id)
78
79
  res = await db.execute(stm)
79
80
  task_list = list(res.scalars().all())
80
- await db.close()
81
81
  if args_schema is False:
82
82
  for task in task_list:
83
83
  setattr(task, "args_schema_parallel", None)
@@ -89,7 +89,7 @@ async def get_list_task(
89
89
  @router.get("/{task_id}/", response_model=TaskRead)
90
90
  async def get_task(
91
91
  task_id: int,
92
- user: UserOAuth = Depends(current_user_act_ver_prof),
92
+ user: UserOAuth = Depends(get_api_guest),
93
93
  db: AsyncSession = Depends(get_async_db),
94
94
  ) -> TaskRead:
95
95
  """
@@ -103,7 +103,7 @@ async def get_task(
103
103
  async def patch_task(
104
104
  task_id: int,
105
105
  task_update: TaskUpdate,
106
- user: UserOAuth = Depends(current_user_act_ver_prof),
106
+ user: UserOAuth = Depends(get_api_user),
107
107
  db: AsyncSession = Depends(get_async_db),
108
108
  ) -> TaskRead | None:
109
109
  """
@@ -133,7 +133,6 @@ async def patch_task(
133
133
 
134
134
  await db.commit()
135
135
  await db.refresh(db_task)
136
- await db.close()
137
136
  return db_task
138
137
 
139
138
 
@@ -142,7 +141,7 @@ async def create_task(
142
141
  task: TaskCreate,
143
142
  user_group_id: int | None = None,
144
143
  private: bool = False,
145
- user: UserOAuth = Depends(current_user_act_ver_prof),
144
+ user: UserOAuth = Depends(get_api_user),
146
145
  db: AsyncSession = Depends(get_async_db),
147
146
  ) -> TaskRead | None:
148
147
  """
@@ -216,7 +215,6 @@ async def create_task(
216
215
  db.add(db_task_group)
217
216
  await db.commit()
218
217
  await db.refresh(db_task)
219
- await db.close()
220
218
 
221
219
  return db_task
222
220
 
@@ -224,7 +222,7 @@ async def create_task(
224
222
  @router.delete("/{task_id}/", status_code=204)
225
223
  async def delete_task(
226
224
  task_id: int,
227
- user: UserOAuth = Depends(current_user_act_ver_prof),
225
+ user: UserOAuth = Depends(get_api_user),
228
226
  db: AsyncSession = Depends(get_async_db),
229
227
  ) -> Response:
230
228
  """
@@ -14,12 +14,13 @@ from pydantic import BaseModel
14
14
  from pydantic import ValidationError
15
15
  from pydantic import model_validator
16
16
 
17
+ from fractal_server import __VERSION__
17
18
  from fractal_server.app.db import AsyncSession
18
19
  from fractal_server.app.db import get_async_db
19
20
  from fractal_server.app.models import UserOAuth
20
21
  from fractal_server.app.models.v2 import TaskGroupActivityV2
21
22
  from fractal_server.app.models.v2 import TaskGroupV2
22
- from fractal_server.app.routes.auth import current_user_act_ver_prof
23
+ from fractal_server.app.routes.auth import get_api_user
23
24
  from fractal_server.app.routes.aux.validate_user_profile import (
24
25
  validate_user_profile,
25
26
  )
@@ -158,7 +159,7 @@ async def collect_tasks_pip(
158
159
  request_data: CollectionRequestData = Depends(parse_request_data),
159
160
  private: bool = False,
160
161
  user_group_id: int | None = None,
161
- user: UserOAuth = Depends(current_user_act_ver_prof),
162
+ user: UserOAuth = Depends(get_api_user),
162
163
  db: AsyncSession = Depends(get_async_db),
163
164
  ) -> TaskGroupActivityRead:
164
165
  """
@@ -332,6 +333,7 @@ async def collect_tasks_pip(
332
333
  action=TaskGroupActivityAction.COLLECT,
333
334
  pkg_name=task_group.pkg_name,
334
335
  version=task_group.version,
336
+ fractal_server_version=__VERSION__,
335
337
  )
336
338
  db.add(task_group_activity)
337
339
  await db.commit()
@@ -12,7 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
12
12
  from fractal_server.app.db import get_async_db
13
13
  from fractal_server.app.models import UserOAuth
14
14
  from fractal_server.app.models.v2 import TaskGroupV2
15
- from fractal_server.app.routes.auth import current_user_act_ver_prof
15
+ from fractal_server.app.routes.auth import get_api_user
16
16
  from fractal_server.app.routes.aux.validate_user_profile import (
17
17
  validate_user_profile,
18
18
  )
@@ -43,7 +43,7 @@ async def collect_task_custom(
43
43
  task_collect: TaskCollectCustom,
44
44
  private: bool = False,
45
45
  user_group_id: int | None = None,
46
- user: UserOAuth = Depends(current_user_act_ver_prof),
46
+ user: UserOAuth = Depends(get_api_user),
47
47
  db: AsyncSession = Depends(get_async_db),
48
48
  ) -> list[TaskRead]:
49
49
  # Get validated resource and profile
@@ -10,6 +10,7 @@ from fastapi import Response
10
10
  from fastapi import UploadFile
11
11
  from fastapi import status
12
12
 
13
+ from fractal_server import __VERSION__
13
14
  from fractal_server.app.db import AsyncSession
14
15
  from fractal_server.app.db import get_async_db
15
16
  from fractal_server.app.models import UserOAuth
@@ -27,7 +28,7 @@ from fractal_server.app.routes.api.v2._aux_functions_tasks import (
27
28
  from fractal_server.app.routes.api.v2._aux_functions_tasks import (
28
29
  _verify_non_duplication_user_constraint,
29
30
  )
30
- from fractal_server.app.routes.auth import current_user_act_ver_prof
31
+ from fractal_server.app.routes.auth import get_api_user
31
32
  from fractal_server.app.routes.aux.validate_user_profile import (
32
33
  validate_user_profile,
33
34
  )
@@ -83,7 +84,7 @@ async def collect_task_pixi(
83
84
  pixi_version: NonEmptyStr | None = Form(None),
84
85
  private: bool = False,
85
86
  user_group_id: int | None = None,
86
- user: UserOAuth = Depends(current_user_act_ver_prof),
87
+ user: UserOAuth = Depends(get_api_user),
87
88
  db: AsyncSession = Depends(get_async_db),
88
89
  ) -> TaskGroupActivityRead:
89
90
  # Get validated resource and profile
@@ -182,6 +183,7 @@ async def collect_task_pixi(
182
183
  action=TaskGroupActivityAction.COLLECT,
183
184
  pkg_name=task_group.pkg_name,
184
185
  version=task_group.version,
186
+ fractal_server_version=__VERSION__,
185
187
  )
186
188
  db.add(task_group_activity)
187
189
  await db.commit()
@@ -4,9 +4,6 @@ from fastapi import APIRouter
4
4
  from fastapi import Depends
5
5
  from fastapi import HTTPException
6
6
  from fastapi import status
7
- from packaging.version import InvalidVersion
8
- from packaging.version import Version
9
- from packaging.version import parse
10
7
  from pydantic.types import AwareDatetime
11
8
  from sqlmodel import or_
12
9
  from sqlmodel import select
@@ -17,13 +14,15 @@ from fractal_server.app.models import LinkUserGroup
17
14
  from fractal_server.app.models import UserOAuth
18
15
  from fractal_server.app.models.v2 import TaskGroupActivityV2
19
16
  from fractal_server.app.models.v2 import TaskGroupV2
20
- from fractal_server.app.routes.auth import current_user_act_ver_prof
17
+ from fractal_server.app.routes.auth import get_api_guest
18
+ from fractal_server.app.routes.auth import get_api_user
21
19
  from fractal_server.app.routes.auth._aux_auth import (
22
20
  _get_default_usergroup_id_or_none,
23
21
  )
24
22
  from fractal_server.app.routes.auth._aux_auth import (
25
23
  _verify_user_belongs_to_group,
26
24
  )
25
+ from fractal_server.app.routes.aux._versions import _version_sort_key
27
26
  from fractal_server.app.schemas.v2 import TaskGroupActivityAction
28
27
  from fractal_server.app.schemas.v2 import TaskGroupActivityRead
29
28
  from fractal_server.app.schemas.v2 import TaskGroupActivityStatus
@@ -42,26 +41,6 @@ router = APIRouter()
42
41
  logger = set_logger(__name__)
43
42
 
44
43
 
45
- def _version_sort_key(
46
- task_group: TaskGroupV2,
47
- ) -> tuple[int, Version | str | None]:
48
- """
49
- Returns a tuple used as (reverse) ordering key for TaskGroups in
50
- `get_task_group_list`.
51
- The TaskGroups with a parsable versions are the first in order,
52
- sorted according to the sorting rules of packaging.version.Version.
53
- Next in order we have the TaskGroups with non-null non-parsable versions,
54
- sorted alphabetically.
55
- Last we have the TaskGroups with null version.
56
- """
57
- if task_group.version is None:
58
- return (0, task_group.version)
59
- try:
60
- return (2, parse(task_group.version))
61
- except InvalidVersion:
62
- return (1, task_group.version)
63
-
64
-
65
44
  @router.get("/activity/", response_model=list[TaskGroupActivityRead])
66
45
  async def get_task_group_activity_list(
67
46
  task_group_activity_id: int | None = None,
@@ -70,7 +49,7 @@ async def get_task_group_activity_list(
70
49
  status: TaskGroupActivityStatus | None = None,
71
50
  action: TaskGroupActivityAction | None = None,
72
51
  timestamp_started_min: AwareDatetime | None = None,
73
- user: UserOAuth = Depends(current_user_act_ver_prof),
52
+ user: UserOAuth = Depends(get_api_guest),
74
53
  db: AsyncSession = Depends(get_async_db),
75
54
  ) -> list[TaskGroupActivityRead]:
76
55
  stm = select(TaskGroupActivityV2).where(
@@ -102,7 +81,7 @@ async def get_task_group_activity_list(
102
81
  )
103
82
  async def get_task_group_activity(
104
83
  task_group_activity_id: int,
105
- user: UserOAuth = Depends(current_user_act_ver_prof),
84
+ user: UserOAuth = Depends(get_api_guest),
106
85
  db: AsyncSession = Depends(get_async_db),
107
86
  ) -> TaskGroupActivityRead:
108
87
  activity = await db.get(TaskGroupActivityV2, task_group_activity_id)
@@ -126,7 +105,7 @@ async def get_task_group_activity(
126
105
 
127
106
  @router.get("/", response_model=list[tuple[str, list[TaskGroupRead]]])
128
107
  async def get_task_group_list(
129
- user: UserOAuth = Depends(current_user_act_ver_prof),
108
+ user: UserOAuth = Depends(get_api_guest),
130
109
  db: AsyncSession = Depends(get_async_db),
131
110
  only_active: bool = False,
132
111
  only_owner: bool = False,
@@ -163,6 +142,7 @@ async def get_task_group_list(
163
142
  if args_schema is False:
164
143
  for taskgroup in task_groups:
165
144
  for task in taskgroup.task_list:
145
+ db.expunge(task) # See issue 3101
166
146
  setattr(task, "args_schema_non_parallel", None)
167
147
  setattr(task, "args_schema_parallel", None)
168
148
 
@@ -174,7 +154,7 @@ async def get_task_group_list(
174
154
  await remove_duplicate_task_groups(
175
155
  task_groups=sorted(
176
156
  list(groups),
177
- key=_version_sort_key,
157
+ key=lambda group: _version_sort_key(group.version),
178
158
  reverse=True,
179
159
  ),
180
160
  user_id=user.id,
@@ -193,7 +173,7 @@ async def get_task_group_list(
193
173
  @router.get("/{task_group_id}/", response_model=TaskGroupRead)
194
174
  async def get_task_group(
195
175
  task_group_id: int,
196
- user: UserOAuth = Depends(current_user_act_ver_prof),
176
+ user: UserOAuth = Depends(get_api_guest),
197
177
  db: AsyncSession = Depends(get_async_db),
198
178
  ) -> TaskGroupRead:
199
179
  """
@@ -211,7 +191,7 @@ async def get_task_group(
211
191
  async def patch_task_group(
212
192
  task_group_id: int,
213
193
  task_group_update: TaskGroupUpdate,
214
- user: UserOAuth = Depends(current_user_act_ver_prof),
194
+ user: UserOAuth = Depends(get_api_user),
215
195
  db: AsyncSession = Depends(get_async_db),
216
196
  ) -> TaskGroupRead:
217
197
  """
@@ -5,11 +5,12 @@ from fastapi import HTTPException
5
5
  from fastapi import Response
6
6
  from fastapi import status
7
7
 
8
+ from fractal_server import __VERSION__
8
9
  from fractal_server.app.db import AsyncSession
9
10
  from fractal_server.app.db import get_async_db
10
11
  from fractal_server.app.models import UserOAuth
11
12
  from fractal_server.app.models.v2 import TaskGroupActivityV2
12
- from fractal_server.app.routes.auth import current_user_act_ver_prof
13
+ from fractal_server.app.routes.auth import get_api_user
13
14
  from fractal_server.app.routes.aux.validate_user_profile import (
14
15
  validate_user_profile,
15
16
  )
@@ -51,7 +52,7 @@ async def deactivate_task_group(
51
52
  task_group_id: int,
52
53
  background_tasks: BackgroundTasks,
53
54
  response: Response,
54
- user: UserOAuth = Depends(current_user_act_ver_prof),
55
+ user: UserOAuth = Depends(get_api_user),
55
56
  db: AsyncSession = Depends(get_async_db),
56
57
  ) -> TaskGroupActivityRead:
57
58
  """
@@ -99,6 +100,7 @@ async def deactivate_task_group(
99
100
  ),
100
101
  timestamp_started=get_timestamp(),
101
102
  timestamp_ended=get_timestamp(),
103
+ fractal_server_version=__VERSION__,
102
104
  )
103
105
  db.add(task_group)
104
106
  db.add(task_group_activity)
@@ -114,6 +116,7 @@ async def deactivate_task_group(
114
116
  pkg_name=task_group.pkg_name,
115
117
  version=task_group.version,
116
118
  timestamp_started=get_timestamp(),
119
+ fractal_server_version=__VERSION__,
117
120
  )
118
121
  task_group.active = False
119
122
  db.add(task_group)
@@ -155,7 +158,7 @@ async def reactivate_task_group(
155
158
  task_group_id: int,
156
159
  background_tasks: BackgroundTasks,
157
160
  response: Response,
158
- user: UserOAuth = Depends(current_user_act_ver_prof),
161
+ user: UserOAuth = Depends(get_api_user),
159
162
  db: AsyncSession = Depends(get_async_db),
160
163
  ) -> TaskGroupRead:
161
164
  """
@@ -202,6 +205,7 @@ async def reactivate_task_group(
202
205
  ),
203
206
  timestamp_started=get_timestamp(),
204
207
  timestamp_ended=get_timestamp(),
208
+ fractal_server_version=__VERSION__,
205
209
  )
206
210
  db.add(task_group)
207
211
  db.add(task_group_activity)
@@ -225,6 +229,7 @@ async def reactivate_task_group(
225
229
  pkg_name=task_group.pkg_name,
226
230
  version=task_group.version,
227
231
  timestamp_started=get_timestamp(),
232
+ fractal_server_version=__VERSION__,
228
233
  )
229
234
  db.add(task_group_activity)
230
235
  await db.commit()
@@ -263,7 +268,7 @@ async def delete_task_group(
263
268
  task_group_id: int,
264
269
  background_tasks: BackgroundTasks,
265
270
  response: Response,
266
- user: UserOAuth = Depends(current_user_act_ver_prof),
271
+ user: UserOAuth = Depends(get_api_user),
267
272
  db: AsyncSession = Depends(get_async_db),
268
273
  ) -> TaskGroupActivityRead:
269
274
  """
@@ -288,6 +293,7 @@ async def delete_task_group(
288
293
  pkg_name=task_group.pkg_name,
289
294
  version=(task_group.version or "N/A"),
290
295
  timestamp_started=get_timestamp(),
296
+ fractal_server_version=__VERSION__,
291
297
  )
292
298
  db.add(task_group_activity)
293
299
  await db.commit()