fractal-server 2.6.4__py3-none-any.whl → 2.7.0a1__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 (35) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/models/linkusergroup.py +11 -0
  3. fractal_server/app/models/v2/__init__.py +2 -0
  4. fractal_server/app/models/v2/task.py +27 -0
  5. fractal_server/app/routes/api/v2/__init__.py +4 -0
  6. fractal_server/app/routes/api/v2/_aux_functions.py +0 -61
  7. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +209 -0
  8. fractal_server/app/routes/api/v2/submit.py +16 -3
  9. fractal_server/app/routes/api/v2/task.py +59 -72
  10. fractal_server/app/routes/api/v2/task_collection.py +20 -4
  11. fractal_server/app/routes/api/v2/task_collection_custom.py +44 -18
  12. fractal_server/app/routes/api/v2/task_group.py +138 -0
  13. fractal_server/app/routes/api/v2/workflow.py +24 -3
  14. fractal_server/app/routes/api/v2/workflowtask.py +4 -7
  15. fractal_server/app/routes/auth/_aux_auth.py +72 -29
  16. fractal_server/app/routes/auth/current_user.py +5 -5
  17. fractal_server/app/routes/auth/router.py +0 -2
  18. fractal_server/app/routes/auth/users.py +8 -7
  19. fractal_server/app/schemas/user.py +1 -2
  20. fractal_server/app/schemas/v2/__init__.py +5 -0
  21. fractal_server/app/schemas/v2/task.py +2 -1
  22. fractal_server/app/schemas/v2/task_group.py +23 -0
  23. fractal_server/app/schemas/v2/workflow.py +5 -0
  24. fractal_server/app/schemas/v2/workflowtask.py +4 -0
  25. fractal_server/migrations/versions/7cf1baae8fb4_task_group_v2.py +66 -0
  26. fractal_server/migrations/versions/df7cc3501bf7_linkusergroup_timestamp_created.py +42 -0
  27. fractal_server/tasks/v2/background_operations.py +16 -35
  28. fractal_server/tasks/v2/background_operations_ssh.py +15 -2
  29. fractal_server/tasks/v2/database_operations.py +54 -0
  30. {fractal_server-2.6.4.dist-info → fractal_server-2.7.0a1.dist-info}/METADATA +1 -1
  31. {fractal_server-2.6.4.dist-info → fractal_server-2.7.0a1.dist-info}/RECORD +34 -29
  32. fractal_server/app/routes/auth/group_names.py +0 -34
  33. {fractal_server-2.6.4.dist-info → fractal_server-2.7.0a1.dist-info}/LICENSE +0 -0
  34. {fractal_server-2.6.4.dist-info → fractal_server-2.7.0a1.dist-info}/WHEEL +0 -0
  35. {fractal_server-2.6.4.dist-info → fractal_server-2.7.0a1.dist-info}/entry_points.txt +0 -0
@@ -1,31 +1,40 @@
1
1
  import shlex
2
2
  import subprocess # nosec
3
3
  from pathlib import Path
4
+ from typing import Optional
4
5
 
5
6
  from fastapi import APIRouter
6
7
  from fastapi import Depends
7
8
  from fastapi import HTTPException
8
9
  from fastapi import status
10
+ from sqlalchemy.ext.asyncio import AsyncSession
9
11
  from sqlmodel import select
10
12
 
11
- from .....config import get_settings
12
- from .....logger import set_logger
13
- from .....syringe import Inject
14
- from ....db import DBSyncSession
15
- from ....db import get_sync_db
16
- from ....models.v1 import Task as TaskV1
17
- from ....models.v2 import TaskV2
18
- from ....schemas.v2 import TaskCollectCustomV2
19
- from ....schemas.v2 import TaskCreateV2
20
- from ....schemas.v2 import TaskReadV2
21
- from ...aux.validate_user_settings import verify_user_has_settings
13
+ from ._aux_functions_tasks import _get_valid_user_group_id
14
+ from fractal_server.app.db import DBSyncSession
15
+ from fractal_server.app.db import get_async_db
16
+ from fractal_server.app.db import get_sync_db
22
17
  from fractal_server.app.models import UserOAuth
18
+ from fractal_server.app.models.v1 import Task as TaskV1
19
+ from fractal_server.app.models.v2 import TaskV2
23
20
  from fractal_server.app.routes.auth import current_active_verified_user
21
+ from fractal_server.app.routes.aux.validate_user_settings import (
22
+ verify_user_has_settings,
23
+ )
24
+ from fractal_server.app.schemas.v2 import TaskCollectCustomV2
25
+ from fractal_server.app.schemas.v2 import TaskCreateV2
26
+ from fractal_server.app.schemas.v2 import TaskGroupCreateV2
27
+ from fractal_server.app.schemas.v2 import TaskReadV2
28
+ from fractal_server.config import get_settings
29
+ from fractal_server.logger import set_logger
24
30
  from fractal_server.string_tools import validate_cmd
25
- from fractal_server.tasks.v2.background_operations import _insert_tasks
31
+ from fractal_server.syringe import Inject
26
32
  from fractal_server.tasks.v2.background_operations import (
27
33
  _prepare_tasks_metadata,
28
34
  )
35
+ from fractal_server.tasks.v2.database_operations import (
36
+ create_db_task_group_and_tasks,
37
+ )
29
38
 
30
39
  router = APIRouter()
31
40
 
@@ -37,12 +46,25 @@ logger = set_logger(__name__)
37
46
  )
38
47
  async def collect_task_custom(
39
48
  task_collect: TaskCollectCustomV2,
49
+ private: bool = False,
50
+ user_group_id: Optional[int] = None,
40
51
  user: UserOAuth = Depends(current_active_verified_user),
41
- db: DBSyncSession = Depends(get_sync_db),
52
+ db: AsyncSession = Depends(get_async_db), # FIXME: using both sync/async
53
+ db_sync: DBSyncSession = Depends(
54
+ get_sync_db
55
+ ), # FIXME: using both sync/async
42
56
  ) -> list[TaskReadV2]:
43
57
 
44
58
  settings = Inject(get_settings)
45
59
 
60
+ # Validate query parameters related to user-group ownership
61
+ user_group_id = await _get_valid_user_group_id(
62
+ user_group_id=user_group_id,
63
+ private=private,
64
+ user_id=user.id,
65
+ db=db,
66
+ )
67
+
46
68
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
47
69
  if task_collect.package_root is None:
48
70
  raise HTTPException(
@@ -140,7 +162,7 @@ async def collect_task_custom(
140
162
  # already guaranteed by a constraint in the table definition).
141
163
  sources = [task.source for task in task_list]
142
164
  stm = select(TaskV2).where(TaskV2.source.in_(sources))
143
- res = db.execute(stm)
165
+ res = db_sync.execute(stm)
144
166
  overlapping_sources_v2 = res.scalars().all()
145
167
  if overlapping_sources_v2:
146
168
  overlapping_tasks_v2_source_and_id = [
@@ -152,7 +174,7 @@ async def collect_task_custom(
152
174
  detail="\n".join(overlapping_tasks_v2_source_and_id),
153
175
  )
154
176
  stm = select(TaskV1).where(TaskV1.source.in_(sources))
155
- res = db.execute(stm)
177
+ res = db_sync.execute(stm)
156
178
  overlapping_sources_v1 = res.scalars().all()
157
179
  if overlapping_sources_v1:
158
180
  overlapping_tasks_v1_source_and_id = [
@@ -164,8 +186,12 @@ async def collect_task_custom(
164
186
  detail="\n".join(overlapping_tasks_v1_source_and_id),
165
187
  )
166
188
 
167
- task_list_db: list[TaskV2] = _insert_tasks(
168
- task_list=task_list, owner=owner, db=db
189
+ task_group = create_db_task_group_and_tasks(
190
+ task_list=task_list,
191
+ task_group_obj=TaskGroupCreateV2(),
192
+ user_id=user.id,
193
+ user_group_id=user_group_id,
194
+ db=db_sync,
169
195
  )
170
196
 
171
197
  logger.debug(
@@ -173,4 +199,4 @@ async def collect_task_custom(
173
199
  f"for package with {source=}"
174
200
  )
175
201
 
176
- return task_list_db
202
+ return task_group.task_list
@@ -0,0 +1,138 @@
1
+ from fastapi import APIRouter
2
+ from fastapi import Depends
3
+ from fastapi import HTTPException
4
+ from fastapi import Response
5
+ from fastapi import status
6
+ from sqlmodel import or_
7
+ from sqlmodel import select
8
+
9
+ from ._aux_functions_tasks import _get_task_group_full_access
10
+ from ._aux_functions_tasks import _get_task_group_read_access
11
+ from fractal_server.app.db import AsyncSession
12
+ from fractal_server.app.db import get_async_db
13
+ from fractal_server.app.models import LinkUserGroup
14
+ from fractal_server.app.models import UserOAuth
15
+ from fractal_server.app.models.v2 import TaskGroupV2
16
+ from fractal_server.app.models.v2 import WorkflowTaskV2
17
+ from fractal_server.app.routes.auth import current_active_user
18
+ from fractal_server.app.routes.auth._aux_auth import (
19
+ _verify_user_belongs_to_group,
20
+ )
21
+ from fractal_server.app.schemas.v2 import TaskGroupReadV2
22
+ from fractal_server.app.schemas.v2 import TaskGroupUpdateV2
23
+ from fractal_server.logger import set_logger
24
+
25
+ router = APIRouter()
26
+
27
+ logger = set_logger(__name__)
28
+
29
+
30
+ @router.get("/", response_model=list[TaskGroupReadV2])
31
+ async def get_task_group_list(
32
+ user: UserOAuth = Depends(current_active_user),
33
+ db: AsyncSession = Depends(get_async_db),
34
+ only_active: bool = False,
35
+ only_owner: bool = False,
36
+ ) -> list[TaskGroupReadV2]:
37
+ """
38
+ Get all accessible TaskGroups
39
+ """
40
+
41
+ if only_owner:
42
+ condition = TaskGroupV2.user_id == user.id
43
+ else:
44
+ condition = or_(
45
+ TaskGroupV2.user_id == user.id,
46
+ TaskGroupV2.user_group_id.in_(
47
+ select(LinkUserGroup.group_id).where(
48
+ LinkUserGroup.user_id == user.id
49
+ )
50
+ ),
51
+ )
52
+ stm = select(TaskGroupV2).where(condition)
53
+ if only_active:
54
+ stm = stm.where(TaskGroupV2.active)
55
+
56
+ res = await db.execute(stm)
57
+ task_groups = res.scalars().all()
58
+
59
+ return task_groups
60
+
61
+
62
+ @router.get("/{task_group_id}/", response_model=TaskGroupReadV2)
63
+ async def get_task_group(
64
+ task_group_id: int,
65
+ user: UserOAuth = Depends(current_active_user),
66
+ db: AsyncSession = Depends(get_async_db),
67
+ ) -> TaskGroupReadV2:
68
+ """
69
+ Get single TaskGroup
70
+ """
71
+ task_group = await _get_task_group_read_access(
72
+ task_group_id=task_group_id,
73
+ user_id=user.id,
74
+ db=db,
75
+ )
76
+ return task_group
77
+
78
+
79
+ @router.delete("/{task_group_id}/", status_code=204)
80
+ async def delete_task_group(
81
+ task_group_id: int,
82
+ user: UserOAuth = Depends(current_active_user),
83
+ db: AsyncSession = Depends(get_async_db),
84
+ ):
85
+ """
86
+ Delete single TaskGroup
87
+ """
88
+
89
+ task_group = await _get_task_group_full_access(
90
+ task_group_id=task_group_id,
91
+ user_id=user.id,
92
+ db=db,
93
+ )
94
+
95
+ stm = select(WorkflowTaskV2).where(
96
+ WorkflowTaskV2.task_id.in_({task.id for task in task_group.task_list})
97
+ )
98
+ res = await db.execute(stm)
99
+ workflow_tasks = res.scalars().all()
100
+ if workflow_tasks != []:
101
+ raise HTTPException(
102
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
103
+ detail=f"TaskV2 {workflow_tasks[0].task_id} is still in use",
104
+ )
105
+
106
+ await db.delete(task_group)
107
+ await db.commit()
108
+
109
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
110
+
111
+
112
+ @router.patch("/{task_group_id}/", response_model=TaskGroupReadV2)
113
+ async def patch_task_group(
114
+ task_group_id: int,
115
+ task_group_update: TaskGroupUpdateV2,
116
+ user: UserOAuth = Depends(current_active_user),
117
+ db: AsyncSession = Depends(get_async_db),
118
+ ) -> TaskGroupReadV2:
119
+ """
120
+ Patch single TaskGroup
121
+ """
122
+ task_group = await _get_task_group_full_access(
123
+ task_group_id=task_group_id,
124
+ user_id=user.id,
125
+ db=db,
126
+ )
127
+
128
+ for key, value in task_group_update.dict(exclude_unset=True).items():
129
+ if (key == "user_group_id") and (value is not None):
130
+ await _verify_user_belongs_to_group(
131
+ user_id=user.id, user_group_id=value, db=db
132
+ )
133
+ setattr(task_group, key, value)
134
+
135
+ db.add(task_group)
136
+ await db.commit()
137
+ await db.refresh(task_group)
138
+ return task_group
@@ -19,6 +19,7 @@ from ....schemas.v2 import WorkflowCreateV2
19
19
  from ....schemas.v2 import WorkflowExportV2
20
20
  from ....schemas.v2 import WorkflowImportV2
21
21
  from ....schemas.v2 import WorkflowReadV2
22
+ from ....schemas.v2 import WorkflowReadV2WithWarnings
22
23
  from ....schemas.v2 import WorkflowTaskCreateV2
23
24
  from ....schemas.v2 import WorkflowUpdateV2
24
25
  from ._aux_functions import _check_workflow_exists
@@ -26,6 +27,7 @@ from ._aux_functions import _get_project_check_owner
26
27
  from ._aux_functions import _get_submitted_jobs_statement
27
28
  from ._aux_functions import _get_workflow_check_owner
28
29
  from ._aux_functions import _workflow_insert_task
30
+ from ._aux_functions_tasks import _get_task_group_read_access
29
31
  from fractal_server.app.models import UserOAuth
30
32
  from fractal_server.app.routes.auth import current_active_user
31
33
 
@@ -89,14 +91,14 @@ async def create_workflow(
89
91
 
90
92
  @router.get(
91
93
  "/project/{project_id}/workflow/{workflow_id}/",
92
- response_model=WorkflowReadV2,
94
+ response_model=WorkflowReadV2WithWarnings,
93
95
  )
94
96
  async def read_workflow(
95
97
  project_id: int,
96
98
  workflow_id: int,
97
99
  user: UserOAuth = Depends(current_active_user),
98
100
  db: AsyncSession = Depends(get_async_db),
99
- ) -> Optional[WorkflowReadV2]:
101
+ ) -> Optional[WorkflowReadV2WithWarnings]:
100
102
  """
101
103
  Get info on an existing workflow
102
104
  """
@@ -108,7 +110,26 @@ async def read_workflow(
108
110
  db=db,
109
111
  )
110
112
 
111
- return workflow
113
+ workflow_data = dict(
114
+ **workflow.model_dump(),
115
+ project=workflow.project,
116
+ )
117
+ task_list_with_warnings = []
118
+ for wftask in workflow.task_list:
119
+ wftask_data = dict(wftask.model_dump(), task=wftask.task)
120
+ try:
121
+ task_group = await _get_task_group_read_access(
122
+ task_group_id=wftask.task.taskgroupv2_id,
123
+ user_id=user.id,
124
+ db=db,
125
+ )
126
+ if not task_group.active:
127
+ wftask_data["warning"] = "Task is not active."
128
+ except HTTPException:
129
+ wftask_data["warning"] = "Current user has no access to this task."
130
+ task_list_with_warnings.append(wftask_data)
131
+ workflow_data["task_list"] = task_list_with_warnings
132
+ return workflow_data
112
133
 
113
134
 
114
135
  @router.patch(
@@ -9,13 +9,13 @@ from fastapi import status
9
9
 
10
10
  from ....db import AsyncSession
11
11
  from ....db import get_async_db
12
- from ....models.v2 import TaskV2
13
12
  from ....schemas.v2 import WorkflowTaskCreateV2
14
13
  from ....schemas.v2 import WorkflowTaskReadV2
15
14
  from ....schemas.v2 import WorkflowTaskUpdateV2
16
15
  from ._aux_functions import _get_workflow_check_owner
17
16
  from ._aux_functions import _get_workflow_task_check_owner
18
17
  from ._aux_functions import _workflow_insert_task
18
+ from ._aux_functions_tasks import _get_task_read_access
19
19
  from fractal_server.app.models import UserOAuth
20
20
  from fractal_server.app.routes.auth import current_active_user
21
21
 
@@ -43,12 +43,9 @@ async def create_workflowtask(
43
43
  project_id=project_id, workflow_id=workflow_id, user_id=user.id, db=db
44
44
  )
45
45
 
46
- task = await db.get(TaskV2, task_id)
47
- if not task:
48
- raise HTTPException(
49
- status_code=status.HTTP_404_NOT_FOUND,
50
- detail=f"TaskV2 {task_id} not found.",
51
- )
46
+ task = await _get_task_read_access(
47
+ task_id=task_id, user_id=user.id, db=db, require_active=True
48
+ )
52
49
 
53
50
  if task.type == "parallel":
54
51
  if (
@@ -1,6 +1,7 @@
1
1
  from fastapi import HTTPException
2
2
  from fastapi import status
3
3
  from sqlalchemy.ext.asyncio import AsyncSession
4
+ from sqlmodel import asc
4
5
  from sqlmodel import select
5
6
 
6
7
  from fractal_server.app.models.linkusergroup import LinkUserGroup
@@ -8,58 +9,59 @@ from fractal_server.app.models.security import UserGroup
8
9
  from fractal_server.app.models.security import UserOAuth
9
10
  from fractal_server.app.schemas.user import UserRead
10
11
  from fractal_server.app.schemas.user_group import UserGroupRead
12
+ from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
13
+ from fractal_server.logger import set_logger
11
14
 
15
+ logger = set_logger(__name__)
12
16
 
13
- async def _get_single_user_with_group_names(
17
+
18
+ async def _get_single_user_with_groups(
14
19
  user: UserOAuth,
15
20
  db: AsyncSession,
16
21
  ) -> UserRead:
17
22
  """
18
- Enrich a user object by filling its `group_names` attribute.
23
+ Enrich a user object by filling its `group_ids_names` attribute.
19
24
 
20
25
  Arguments:
21
26
  user: The current `UserOAuth` object
22
27
  db: Async db session
23
28
 
24
29
  Returns:
25
- A `UserRead` object with `group_names` set
30
+ A `UserRead` object with `group_ids_names` dict
26
31
  """
27
32
  stm_groups = (
28
33
  select(UserGroup)
29
34
  .join(LinkUserGroup)
30
- .where(LinkUserGroup.user_id == UserOAuth.id)
35
+ .where(LinkUserGroup.user_id == user.id)
36
+ .order_by(asc(LinkUserGroup.timestamp_created))
31
37
  )
32
38
  res = await db.execute(stm_groups)
33
39
  groups = res.scalars().unique().all()
34
- group_names = [group.name for group in groups]
35
- return UserRead(
36
- **user.model_dump(),
37
- group_names=group_names,
38
- oauth_accounts=user.oauth_accounts,
40
+ group_ids_names = [(group.id, group.name) for group in groups]
41
+
42
+ # Check that Fractal Default Group is the first of the list. If not, fix.
43
+ index = next(
44
+ (
45
+ i
46
+ for i, group_tuple in enumerate(group_ids_names)
47
+ if group_tuple[1] == FRACTAL_DEFAULT_GROUP_NAME
48
+ ),
49
+ None,
39
50
  )
51
+ if index is None:
52
+ logger.warning(
53
+ f"User {user.id} not in "
54
+ f"default UserGroup '{FRACTAL_DEFAULT_GROUP_NAME}'"
55
+ )
56
+ elif index != 0:
57
+ default_group = group_ids_names.pop(index)
58
+ group_ids_names.insert(0, default_group)
59
+ else:
60
+ pass
40
61
 
41
-
42
- async def _get_single_user_with_group_ids(
43
- user: UserOAuth,
44
- db: AsyncSession,
45
- ) -> UserRead:
46
- """
47
- Enrich a user object by filling its `group_ids` attribute.
48
-
49
- Arguments:
50
- user: The current `UserOAuth` object
51
- db: Async db session
52
-
53
- Returns:
54
- A `UserRead` object with `group_ids` set
55
- """
56
- stm_links = select(LinkUserGroup).where(LinkUserGroup.user_id == user.id)
57
- res = await db.execute(stm_links)
58
- links = res.scalars().unique().all()
59
- group_ids = [link.group_id for link in links]
60
62
  return UserRead(
61
63
  **user.model_dump(),
62
- group_ids=group_ids,
64
+ group_ids_names=group_ids_names,
63
65
  oauth_accounts=user.oauth_accounts,
64
66
  )
65
67
 
@@ -111,3 +113,44 @@ async def _user_or_404(user_id: int, db: AsyncSession) -> UserOAuth:
111
113
  status_code=status.HTTP_404_NOT_FOUND, detail="User not found."
112
114
  )
113
115
  return user
116
+
117
+
118
+ async def _get_default_user_group_id(db: AsyncSession) -> int:
119
+ stm = select(UserGroup.id).where(
120
+ UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
121
+ )
122
+ res = await db.execute(stm)
123
+ user_group_id = res.scalars().one_or_none()
124
+ if user_group_id is None:
125
+ raise HTTPException(
126
+ status_code=status.HTTP_404_NOT_FOUND,
127
+ detail=f"User group '{FRACTAL_DEFAULT_GROUP_NAME}' not found.",
128
+ )
129
+ return user_group_id
130
+
131
+
132
+ async def _verify_user_belongs_to_group(
133
+ *, user_id: int, user_group_id: int, db: AsyncSession
134
+ ):
135
+ stm = (
136
+ select(LinkUserGroup)
137
+ .where(LinkUserGroup.user_id == user_id)
138
+ .where(LinkUserGroup.group_id == user_group_id)
139
+ )
140
+ res = await db.execute(stm)
141
+ link = res.scalars().one_or_none()
142
+ if link is None:
143
+ group = await db.get(UserGroup, user_group_id)
144
+ if group is None:
145
+ raise HTTPException(
146
+ status_code=status.HTTP_404_NOT_FOUND,
147
+ detail=f"UserGroup {user_group_id} not found",
148
+ )
149
+ else:
150
+ raise HTTPException(
151
+ status_code=status.HTTP_403_FORBIDDEN,
152
+ detail=(
153
+ f"User {user_id} does not belong "
154
+ f"to UserGroup {user_group_id}"
155
+ ),
156
+ )
@@ -13,7 +13,7 @@ from ...schemas.user import UserRead
13
13
  from ...schemas.user import UserUpdate
14
14
  from ...schemas.user import UserUpdateStrict
15
15
  from ..aux.validate_user_settings import verify_user_has_settings
16
- from ._aux_auth import _get_single_user_with_group_names
16
+ from ._aux_auth import _get_single_user_with_groups
17
17
  from fractal_server.app.models import LinkUserGroup
18
18
  from fractal_server.app.models import UserGroup
19
19
  from fractal_server.app.models import UserOAuth
@@ -28,15 +28,15 @@ router_current_user = APIRouter()
28
28
 
29
29
  @router_current_user.get("/current-user/", response_model=UserRead)
30
30
  async def get_current_user(
31
- group_names: bool = False,
31
+ group_ids_names: bool = False,
32
32
  user: UserOAuth = Depends(current_active_user),
33
33
  db: AsyncSession = Depends(get_async_db),
34
34
  ):
35
35
  """
36
36
  Return current user
37
37
  """
38
- if group_names is True:
39
- user_with_groups = await _get_single_user_with_group_names(user, db)
38
+ if group_ids_names is True:
39
+ user_with_groups = await _get_single_user_with_groups(user, db)
40
40
  return user_with_groups
41
41
  else:
42
42
  return user
@@ -65,7 +65,7 @@ async def patch_current_user(
65
65
  patched_user = await db.get(
66
66
  UserOAuth, validated_user.id, populate_existing=True
67
67
  )
68
- patched_user_with_groups = await _get_single_user_with_group_names(
68
+ patched_user_with_groups = await _get_single_user_with_groups(
69
69
  patched_user, db
70
70
  )
71
71
  return patched_user_with_groups
@@ -2,7 +2,6 @@ from fastapi import APIRouter
2
2
 
3
3
  from .current_user import router_current_user
4
4
  from .group import router_group
5
- from .group_names import router_group_names
6
5
  from .login import router_login
7
6
  from .oauth import router_oauth
8
7
  from .register import router_register
@@ -13,7 +12,6 @@ router_auth = APIRouter()
13
12
  router_auth.include_router(router_register)
14
13
  router_auth.include_router(router_current_user)
15
14
  router_auth.include_router(router_login)
16
- router_auth.include_router(router_group_names)
17
15
  router_auth.include_router(router_users)
18
16
  router_auth.include_router(router_group)
19
17
  router_auth.include_router(router_oauth)
@@ -20,7 +20,7 @@ from ...schemas.user import UserRead
20
20
  from ...schemas.user import UserUpdate
21
21
  from ...schemas.user import UserUpdateWithNewGroupIds
22
22
  from ..aux.validate_user_settings import verify_user_has_settings
23
- from ._aux_auth import _get_single_user_with_group_ids
23
+ from ._aux_auth import _get_single_user_with_groups
24
24
  from fractal_server.app.models import LinkUserGroup
25
25
  from fractal_server.app.models import UserGroup
26
26
  from fractal_server.app.models import UserOAuth
@@ -41,13 +41,14 @@ logger = set_logger(__name__)
41
41
  @router_users.get("/users/{user_id}/", response_model=UserRead)
42
42
  async def get_user(
43
43
  user_id: int,
44
- group_ids: bool = True,
44
+ group_ids_names: bool = True,
45
45
  superuser: UserOAuth = Depends(current_active_superuser),
46
46
  db: AsyncSession = Depends(get_async_db),
47
47
  ) -> UserRead:
48
48
  user = await _user_or_404(user_id, db)
49
- if group_ids:
50
- user = await _get_single_user_with_group_ids(user, db)
49
+ if group_ids_names:
50
+ user_with_groups = await _get_single_user_with_groups(user, db)
51
+ return user_with_groups
51
52
  return user
52
53
 
53
54
 
@@ -163,12 +164,12 @@ async def patch_user(
163
164
  # Nothing to do, just continue
164
165
  patched_user = user_to_patch
165
166
 
166
- # Enrich user object with `group_ids` attribute
167
- patched_user_with_group_ids = await _get_single_user_with_group_ids(
167
+ # Enrich user object with `group_ids_names` attribute
168
+ patched_user_with_groups = await _get_single_user_with_groups(
168
169
  patched_user, db
169
170
  )
170
171
 
171
- return patched_user_with_group_ids
172
+ return patched_user_with_groups
172
173
 
173
174
 
174
175
  @router_users.get("/users/", response_model=list[UserRead])
@@ -41,8 +41,7 @@ class UserRead(schemas.BaseUser[int]):
41
41
  """
42
42
 
43
43
  username: Optional[str]
44
- group_names: Optional[list[str]] = None
45
- group_ids: Optional[list[int]] = None
44
+ group_ids_names: Optional[list[tuple[int, str]]] = None
46
45
  oauth_accounts: list[OAuthAccountRead]
47
46
 
48
47
 
@@ -26,14 +26,19 @@ from .task_collection import CollectionStateReadV2 # noqa F401
26
26
  from .task_collection import CollectionStatusV2 # noqa F401
27
27
  from .task_collection import TaskCollectCustomV2 # noqa F401
28
28
  from .task_collection import TaskCollectPipV2 # noqa F401
29
+ from .task_group import TaskGroupCreateV2 # noqa F401
30
+ from .task_group import TaskGroupReadV2 # noqa F401
31
+ from .task_group import TaskGroupUpdateV2 # noqa F401
29
32
  from .workflow import WorkflowCreateV2 # noqa F401
30
33
  from .workflow import WorkflowExportV2 # noqa F401
31
34
  from .workflow import WorkflowImportV2 # noqa F401
32
35
  from .workflow import WorkflowReadV2 # noqa F401
36
+ from .workflow import WorkflowReadV2WithWarnings # noqa F401
33
37
  from .workflow import WorkflowUpdateV2 # noqa F401
34
38
  from .workflowtask import WorkflowTaskCreateV2 # noqa F401
35
39
  from .workflowtask import WorkflowTaskExportV2 # noqa F401
36
40
  from .workflowtask import WorkflowTaskImportV2 # noqa F401
37
41
  from .workflowtask import WorkflowTaskReadV2 # noqa F401
42
+ from .workflowtask import WorkflowTaskReadV2WithWarning # noqa F401
38
43
  from .workflowtask import WorkflowTaskStatusTypeV2 # noqa F401
39
44
  from .workflowtask import WorkflowTaskUpdateV2 # noqa F401
@@ -90,7 +90,6 @@ class TaskReadV2(BaseModel):
90
90
  name: str
91
91
  type: Literal["parallel", "non_parallel", "compound"]
92
92
  source: str
93
- owner: Optional[str]
94
93
  version: Optional[str]
95
94
 
96
95
  command_non_parallel: Optional[str]
@@ -105,6 +104,8 @@ class TaskReadV2(BaseModel):
105
104
  input_types: dict[str, bool]
106
105
  output_types: dict[str, bool]
107
106
 
107
+ taskgroupv2_id: Optional[int]
108
+
108
109
 
109
110
  class TaskUpdateV2(BaseModel):
110
111
 
@@ -0,0 +1,23 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from .task import TaskReadV2
6
+
7
+
8
+ class TaskGroupCreateV2(BaseModel):
9
+ active: bool = True
10
+
11
+
12
+ class TaskGroupReadV2(BaseModel):
13
+
14
+ id: int
15
+ user_id: int
16
+ user_group_id: Optional[int] = None
17
+ active: bool
18
+ task_list: list[TaskReadV2]
19
+
20
+
21
+ class TaskGroupUpdateV2(BaseModel):
22
+ user_group_id: Optional[int] = None
23
+ active: Optional[bool] = None
@@ -11,6 +11,7 @@ from .project import ProjectReadV2
11
11
  from .workflowtask import WorkflowTaskExportV2
12
12
  from .workflowtask import WorkflowTaskImportV2
13
13
  from .workflowtask import WorkflowTaskReadV2
14
+ from .workflowtask import WorkflowTaskReadV2WithWarning
14
15
 
15
16
 
16
17
  class WorkflowCreateV2(BaseModel, extra=Extra.forbid):
@@ -35,6 +36,10 @@ class WorkflowReadV2(BaseModel):
35
36
  )
36
37
 
37
38
 
39
+ class WorkflowReadV2WithWarnings(WorkflowReadV2):
40
+ task_list: list[WorkflowTaskReadV2WithWarning]
41
+
42
+
38
43
  class WorkflowUpdateV2(BaseModel):
39
44
 
40
45
  name: Optional[str]