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.
- fractal_server/__init__.py +1 -1
- fractal_server/app/models/linkusergroup.py +11 -0
- fractal_server/app/models/v2/__init__.py +2 -0
- fractal_server/app/models/v2/task.py +27 -0
- fractal_server/app/routes/api/v2/__init__.py +4 -0
- fractal_server/app/routes/api/v2/_aux_functions.py +0 -61
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +209 -0
- fractal_server/app/routes/api/v2/submit.py +16 -3
- fractal_server/app/routes/api/v2/task.py +59 -72
- fractal_server/app/routes/api/v2/task_collection.py +20 -4
- fractal_server/app/routes/api/v2/task_collection_custom.py +44 -18
- fractal_server/app/routes/api/v2/task_group.py +138 -0
- fractal_server/app/routes/api/v2/workflow.py +24 -3
- fractal_server/app/routes/api/v2/workflowtask.py +4 -7
- fractal_server/app/routes/auth/_aux_auth.py +72 -29
- fractal_server/app/routes/auth/current_user.py +5 -5
- fractal_server/app/routes/auth/router.py +0 -2
- fractal_server/app/routes/auth/users.py +8 -7
- fractal_server/app/schemas/user.py +1 -2
- fractal_server/app/schemas/v2/__init__.py +5 -0
- fractal_server/app/schemas/v2/task.py +2 -1
- fractal_server/app/schemas/v2/task_group.py +23 -0
- fractal_server/app/schemas/v2/workflow.py +5 -0
- fractal_server/app/schemas/v2/workflowtask.py +4 -0
- fractal_server/migrations/versions/7cf1baae8fb4_task_group_v2.py +66 -0
- fractal_server/migrations/versions/df7cc3501bf7_linkusergroup_timestamp_created.py +42 -0
- fractal_server/tasks/v2/background_operations.py +16 -35
- fractal_server/tasks/v2/background_operations_ssh.py +15 -2
- fractal_server/tasks/v2/database_operations.py +54 -0
- {fractal_server-2.6.4.dist-info → fractal_server-2.7.0a1.dist-info}/METADATA +1 -1
- {fractal_server-2.6.4.dist-info → fractal_server-2.7.0a1.dist-info}/RECORD +34 -29
- fractal_server/app/routes/auth/group_names.py +0 -34
- {fractal_server-2.6.4.dist-info → fractal_server-2.7.0a1.dist-info}/LICENSE +0 -0
- {fractal_server-2.6.4.dist-info → fractal_server-2.7.0a1.dist-info}/WHEEL +0 -0
- {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
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
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.
|
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:
|
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 =
|
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 =
|
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
|
-
|
168
|
-
task_list=task_list,
|
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
|
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=
|
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[
|
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
|
-
|
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
|
47
|
-
|
48
|
-
|
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
|
-
|
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 `
|
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 `
|
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 ==
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
39
|
-
user_with_groups = await
|
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
|
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
|
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
|
-
|
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
|
50
|
-
|
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 `
|
167
|
-
|
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
|
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
|
-
|
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]
|