fractal-server 2.18.0__py3-none-any.whl → 2.18.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/__main__.py +1 -2
- fractal_server/app/models/security.py +5 -7
- fractal_server/app/models/v2/job.py +2 -13
- fractal_server/app/models/v2/resource.py +0 -13
- fractal_server/app/routes/admin/v2/__init__.py +12 -10
- fractal_server/app/routes/admin/v2/job.py +15 -15
- fractal_server/app/routes/admin/v2/task.py +7 -7
- fractal_server/app/routes/admin/v2/task_group.py +12 -14
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +20 -20
- fractal_server/app/routes/api/__init__.py +9 -0
- fractal_server/app/routes/api/v2/__init__.py +49 -47
- fractal_server/app/routes/api/v2/_aux_functions.py +47 -22
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +4 -4
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +2 -2
- fractal_server/app/routes/api/v2/dataset.py +60 -66
- fractal_server/app/routes/api/v2/history.py +5 -7
- fractal_server/app/routes/api/v2/job.py +12 -12
- fractal_server/app/routes/api/v2/project.py +11 -11
- fractal_server/app/routes/api/v2/status_legacy.py +29 -15
- fractal_server/app/routes/api/v2/submit.py +66 -65
- fractal_server/app/routes/api/v2/task.py +17 -15
- fractal_server/app/routes/api/v2/task_collection.py +18 -18
- fractal_server/app/routes/api/v2/task_collection_custom.py +13 -11
- fractal_server/app/routes/api/v2/task_collection_pixi.py +9 -9
- fractal_server/app/routes/api/v2/task_group.py +18 -18
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +26 -26
- fractal_server/app/routes/api/v2/task_version_update.py +5 -5
- fractal_server/app/routes/api/v2/workflow.py +18 -18
- fractal_server/app/routes/api/v2/workflow_import.py +11 -11
- fractal_server/app/routes/api/v2/workflowtask.py +37 -10
- fractal_server/app/routes/auth/_aux_auth.py +0 -100
- fractal_server/app/routes/auth/current_user.py +63 -0
- fractal_server/app/routes/auth/group.py +30 -1
- fractal_server/app/routes/auth/router.py +0 -2
- fractal_server/app/routes/auth/users.py +0 -9
- fractal_server/app/schemas/user.py +12 -29
- fractal_server/app/schemas/user_group.py +15 -0
- fractal_server/app/schemas/v2/__init__.py +48 -48
- fractal_server/app/schemas/v2/dataset.py +13 -35
- fractal_server/app/schemas/v2/dumps.py +9 -9
- fractal_server/app/schemas/v2/job.py +11 -11
- fractal_server/app/schemas/v2/project.py +3 -3
- fractal_server/app/schemas/v2/resource.py +4 -13
- fractal_server/app/schemas/v2/status_legacy.py +3 -3
- fractal_server/app/schemas/v2/task.py +6 -6
- fractal_server/app/schemas/v2/task_collection.py +4 -4
- fractal_server/app/schemas/v2/task_group.py +16 -16
- fractal_server/app/schemas/v2/workflow.py +16 -16
- fractal_server/app/schemas/v2/workflowtask.py +14 -14
- fractal_server/app/security/__init__.py +1 -1
- fractal_server/app/shutdown.py +6 -6
- fractal_server/config/__init__.py +6 -0
- fractal_server/config/_data.py +79 -0
- fractal_server/config/_main.py +1 -6
- fractal_server/images/models.py +2 -1
- fractal_server/main.py +11 -72
- fractal_server/runner/config/_slurm.py +0 -2
- fractal_server/runner/executors/slurm_common/slurm_config.py +0 -1
- fractal_server/runner/v2/_local.py +3 -4
- fractal_server/runner/v2/_slurm_ssh.py +3 -4
- fractal_server/runner/v2/_slurm_sudo.py +3 -4
- fractal_server/runner/v2/runner.py +17 -36
- fractal_server/runner/v2/runner_functions.py +14 -11
- fractal_server/runner/v2/submit_workflow.py +9 -22
- fractal_server/tasks/v2/local/_utils.py +2 -2
- fractal_server/tasks/v2/local/collect.py +6 -5
- fractal_server/tasks/v2/local/collect_pixi.py +6 -5
- fractal_server/tasks/v2/local/deactivate.py +7 -7
- fractal_server/tasks/v2/local/deactivate_pixi.py +3 -3
- fractal_server/tasks/v2/local/delete.py +5 -5
- fractal_server/tasks/v2/local/reactivate.py +5 -5
- fractal_server/tasks/v2/local/reactivate_pixi.py +5 -5
- fractal_server/tasks/v2/ssh/collect.py +5 -5
- fractal_server/tasks/v2/ssh/collect_pixi.py +5 -5
- fractal_server/tasks/v2/ssh/deactivate.py +7 -7
- fractal_server/tasks/v2/ssh/deactivate_pixi.py +2 -2
- fractal_server/tasks/v2/ssh/delete.py +5 -5
- fractal_server/tasks/v2/ssh/reactivate.py +5 -5
- fractal_server/tasks/v2/ssh/reactivate_pixi.py +5 -5
- fractal_server/tasks/v2/utils_background.py +7 -7
- fractal_server/tasks/v2/utils_database.py +5 -5
- fractal_server/types/__init__.py +0 -22
- fractal_server/types/validators/__init__.py +0 -3
- fractal_server/types/validators/_common_validators.py +0 -32
- {fractal_server-2.18.0.dist-info → fractal_server-2.18.0a1.dist-info}/METADATA +1 -1
- {fractal_server-2.18.0.dist-info → fractal_server-2.18.0a1.dist-info}/RECORD +90 -95
- fractal_server/app/routes/auth/viewer_paths.py +0 -43
- fractal_server/data_migrations/2_18_0.py +0 -30
- fractal_server/migrations/versions/7910eed4cf97_user_project_dirs_and_usergroup_viewer_.py +0 -60
- fractal_server/migrations/versions/88270f589c9b_add_prevent_new_submissions.py +0 -39
- fractal_server/migrations/versions/f0702066b007_one_submitted_job_per_dataset.py +0 -40
- {fractal_server-2.18.0.dist-info → fractal_server-2.18.0a1.dist-info}/WHEEL +0 -0
- {fractal_server-2.18.0.dist-info → fractal_server-2.18.0a1.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.18.0.dist-info → fractal_server-2.18.0a1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,21 +1,12 @@
|
|
|
1
|
-
from os.path import normpath
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
1
|
from fastapi import HTTPException
|
|
5
2
|
from fastapi import status
|
|
6
3
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
7
|
-
from sqlmodel import and_
|
|
8
4
|
from sqlmodel import asc
|
|
9
|
-
from sqlmodel import not_
|
|
10
|
-
from sqlmodel import or_
|
|
11
5
|
from sqlmodel import select
|
|
12
6
|
|
|
13
7
|
from fractal_server.app.models.linkusergroup import LinkUserGroup
|
|
14
|
-
from fractal_server.app.models.linkuserproject import LinkUserProjectV2
|
|
15
8
|
from fractal_server.app.models.security import UserGroup
|
|
16
9
|
from fractal_server.app.models.security import UserOAuth
|
|
17
|
-
from fractal_server.app.models.v2.dataset import DatasetV2
|
|
18
|
-
from fractal_server.app.models.v2.project import ProjectV2
|
|
19
10
|
from fractal_server.app.schemas.user import UserRead
|
|
20
11
|
from fractal_server.app.schemas.user_group import UserGroupRead
|
|
21
12
|
from fractal_server.config import get_settings
|
|
@@ -187,94 +178,3 @@ async def _verify_user_belongs_to_group(
|
|
|
187
178
|
f"to UserGroup {user_group_id}"
|
|
188
179
|
),
|
|
189
180
|
)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
async def _check_project_dirs_update(
|
|
193
|
-
*,
|
|
194
|
-
old_project_dirs: list[str],
|
|
195
|
-
new_project_dirs: list[str],
|
|
196
|
-
user_id: int,
|
|
197
|
-
db: AsyncSession,
|
|
198
|
-
) -> None:
|
|
199
|
-
"""
|
|
200
|
-
Raises 422 if by replacing user's `project_dirs` with new ones we are
|
|
201
|
-
removing the access to a `zarr_dir` used by some dataset.
|
|
202
|
-
|
|
203
|
-
Note both `old_project_dirs` and `new_project_dirs` have been
|
|
204
|
-
normalized through `os.path.normpath`, which notably strips any trailing
|
|
205
|
-
`/` character. To be safe, we also re-normalize them within this function.
|
|
206
|
-
"""
|
|
207
|
-
# Create a list of all the old project dirs that will lose privileges.
|
|
208
|
-
# E.g.:
|
|
209
|
-
# old_project_dirs = ["/a", "/b", "/c/d", "/e/f"]
|
|
210
|
-
# new_project_dirs = ["/a", "/c", "/e/f/g1", "/e/f/g2"]
|
|
211
|
-
# removed_project_dirs == ["/b", "/e/f"]
|
|
212
|
-
removed_project_dirs = [
|
|
213
|
-
old_project_dir
|
|
214
|
-
for old_project_dir in old_project_dirs
|
|
215
|
-
if not any(
|
|
216
|
-
Path(old_project_dir).is_relative_to(new_project_dir)
|
|
217
|
-
for new_project_dir in new_project_dirs
|
|
218
|
-
)
|
|
219
|
-
]
|
|
220
|
-
if removed_project_dirs:
|
|
221
|
-
# Query all the `zarr_dir`s linked to the user such that `zarr_dir`
|
|
222
|
-
# starts with one of the project dirs in `removed_project_dirs`.
|
|
223
|
-
stmt = (
|
|
224
|
-
select(DatasetV2.zarr_dir)
|
|
225
|
-
.join(ProjectV2, ProjectV2.id == DatasetV2.project_id)
|
|
226
|
-
.join(
|
|
227
|
-
LinkUserProjectV2,
|
|
228
|
-
LinkUserProjectV2.project_id == ProjectV2.id,
|
|
229
|
-
)
|
|
230
|
-
.where(LinkUserProjectV2.user_id == user_id)
|
|
231
|
-
.where(LinkUserProjectV2.is_verified.is_(True))
|
|
232
|
-
.where(
|
|
233
|
-
or_(
|
|
234
|
-
*[
|
|
235
|
-
DatasetV2.zarr_dir.startswith(normpath(old_project_dir))
|
|
236
|
-
for old_project_dir in removed_project_dirs
|
|
237
|
-
]
|
|
238
|
-
)
|
|
239
|
-
)
|
|
240
|
-
)
|
|
241
|
-
if new_project_dirs:
|
|
242
|
-
stmt = stmt.where(
|
|
243
|
-
and_(
|
|
244
|
-
*[
|
|
245
|
-
not_(
|
|
246
|
-
DatasetV2.zarr_dir.startswith(
|
|
247
|
-
normpath(new_project_dir)
|
|
248
|
-
)
|
|
249
|
-
)
|
|
250
|
-
for new_project_dir in new_project_dirs
|
|
251
|
-
]
|
|
252
|
-
)
|
|
253
|
-
)
|
|
254
|
-
res = await db.execute(stmt)
|
|
255
|
-
|
|
256
|
-
# Raise 422 if one of the query results is relative to a path in
|
|
257
|
-
# `removed_project_dirs`, but its not relative to any path in
|
|
258
|
-
# `new_project_dirs`.
|
|
259
|
-
if any(
|
|
260
|
-
(
|
|
261
|
-
any(
|
|
262
|
-
Path(zarr_dir).is_relative_to(old_project_dir)
|
|
263
|
-
for old_project_dir in removed_project_dirs
|
|
264
|
-
)
|
|
265
|
-
and not any(
|
|
266
|
-
Path(zarr_dir).is_relative_to(new_project_dir)
|
|
267
|
-
for new_project_dir in new_project_dirs
|
|
268
|
-
)
|
|
269
|
-
)
|
|
270
|
-
for zarr_dir in res.scalars().all()
|
|
271
|
-
):
|
|
272
|
-
raise HTTPException(
|
|
273
|
-
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
274
|
-
detail=(
|
|
275
|
-
"You tried updating the user project_dirs, removing "
|
|
276
|
-
f"{removed_project_dirs}. This operation is not possible, "
|
|
277
|
-
"because it would make the user loose access to some of "
|
|
278
|
-
"their dataset zarr directories."
|
|
279
|
-
),
|
|
280
|
-
)
|
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
Definition of `/auth/current-user/` endpoints
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import os
|
|
6
|
+
|
|
5
7
|
from fastapi import APIRouter
|
|
6
8
|
from fastapi import Depends
|
|
7
9
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
8
10
|
from sqlmodel import select
|
|
9
11
|
|
|
10
12
|
from fractal_server.app.db import get_async_db
|
|
13
|
+
from fractal_server.app.models import LinkUserGroup
|
|
11
14
|
from fractal_server.app.models import Profile
|
|
12
15
|
from fractal_server.app.models import Resource
|
|
16
|
+
from fractal_server.app.models import UserGroup
|
|
13
17
|
from fractal_server.app.models import UserOAuth
|
|
14
18
|
from fractal_server.app.routes.auth import current_user_act
|
|
19
|
+
from fractal_server.app.routes.auth import current_user_act_ver
|
|
15
20
|
from fractal_server.app.routes.auth._aux_auth import (
|
|
16
21
|
_get_single_user_with_groups,
|
|
17
22
|
)
|
|
@@ -21,6 +26,9 @@ from fractal_server.app.schemas.user import UserUpdate
|
|
|
21
26
|
from fractal_server.app.schemas.user import UserUpdateStrict
|
|
22
27
|
from fractal_server.app.security import UserManager
|
|
23
28
|
from fractal_server.app.security import get_user_manager
|
|
29
|
+
from fractal_server.config import DataAuthScheme
|
|
30
|
+
from fractal_server.config import get_data_settings
|
|
31
|
+
from fractal_server.syringe import Inject
|
|
24
32
|
|
|
25
33
|
router_current_user = APIRouter()
|
|
26
34
|
|
|
@@ -98,3 +106,58 @@ async def get_current_user_profile_info(
|
|
|
98
106
|
)
|
|
99
107
|
|
|
100
108
|
return response_data
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@router_current_user.get(
|
|
112
|
+
"/current-user/allowed-viewer-paths/", response_model=list[str]
|
|
113
|
+
)
|
|
114
|
+
async def get_current_user_allowed_viewer_paths(
|
|
115
|
+
current_user: UserOAuth = Depends(current_user_act_ver),
|
|
116
|
+
db: AsyncSession = Depends(get_async_db),
|
|
117
|
+
) -> list[str]:
|
|
118
|
+
"""
|
|
119
|
+
Returns the allowed viewer paths for current user, according to the
|
|
120
|
+
selected FRACTAL_DATA_AUTH_SCHEME
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
data_settings = Inject(get_data_settings)
|
|
124
|
+
|
|
125
|
+
authorized_paths = []
|
|
126
|
+
|
|
127
|
+
if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.NONE:
|
|
128
|
+
return authorized_paths
|
|
129
|
+
|
|
130
|
+
# Append `project_dir` to the list of authorized paths
|
|
131
|
+
authorized_paths.append(current_user.project_dir)
|
|
132
|
+
|
|
133
|
+
# If auth scheme is "users-folders" and `slurm_user` is set,
|
|
134
|
+
# build and append the user folder
|
|
135
|
+
if (
|
|
136
|
+
data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.USERS_FOLDERS
|
|
137
|
+
and current_user.profile_id is not None
|
|
138
|
+
):
|
|
139
|
+
profile = await db.get(Profile, current_user.profile_id)
|
|
140
|
+
if profile is not None and profile.username is not None:
|
|
141
|
+
base_folder = data_settings.FRACTAL_DATA_BASE_FOLDER
|
|
142
|
+
user_folder = os.path.join(base_folder, profile.username)
|
|
143
|
+
authorized_paths.append(user_folder)
|
|
144
|
+
|
|
145
|
+
if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.VIEWER_PATHS:
|
|
146
|
+
# Returns the union of `viewer_paths` for all user's groups
|
|
147
|
+
cmd = (
|
|
148
|
+
select(UserGroup.viewer_paths)
|
|
149
|
+
.join(LinkUserGroup, LinkUserGroup.group_id == UserGroup.id)
|
|
150
|
+
.where(LinkUserGroup.user_id == current_user.id)
|
|
151
|
+
)
|
|
152
|
+
res = await db.execute(cmd)
|
|
153
|
+
viewer_paths_nested = res.scalars().all()
|
|
154
|
+
|
|
155
|
+
# Flatten a nested object and make its elements unique
|
|
156
|
+
all_viewer_paths_set = {
|
|
157
|
+
path
|
|
158
|
+
for _viewer_paths in viewer_paths_nested
|
|
159
|
+
for path in _viewer_paths
|
|
160
|
+
}
|
|
161
|
+
authorized_paths.extend(all_viewer_paths_set)
|
|
162
|
+
|
|
163
|
+
return authorized_paths
|
|
@@ -16,6 +16,7 @@ from fractal_server.app.models import UserGroup
|
|
|
16
16
|
from fractal_server.app.models import UserOAuth
|
|
17
17
|
from fractal_server.app.schemas.user_group import UserGroupCreate
|
|
18
18
|
from fractal_server.app.schemas.user_group import UserGroupRead
|
|
19
|
+
from fractal_server.app.schemas.user_group import UserGroupUpdate
|
|
19
20
|
from fractal_server.config import get_settings
|
|
20
21
|
from fractal_server.logger import set_logger
|
|
21
22
|
from fractal_server.syringe import Inject
|
|
@@ -100,13 +101,41 @@ async def create_single_group(
|
|
|
100
101
|
)
|
|
101
102
|
|
|
102
103
|
# Create and return new group
|
|
103
|
-
new_group = UserGroup(
|
|
104
|
+
new_group = UserGroup(
|
|
105
|
+
name=group_create.name, viewer_paths=group_create.viewer_paths
|
|
106
|
+
)
|
|
104
107
|
db.add(new_group)
|
|
105
108
|
await db.commit()
|
|
106
109
|
|
|
107
110
|
return dict(new_group.model_dump(), user_ids=[])
|
|
108
111
|
|
|
109
112
|
|
|
113
|
+
@router_group.patch(
|
|
114
|
+
"/group/{group_id}/",
|
|
115
|
+
response_model=UserGroupRead,
|
|
116
|
+
status_code=status.HTTP_200_OK,
|
|
117
|
+
)
|
|
118
|
+
async def update_single_group(
|
|
119
|
+
group_id: int,
|
|
120
|
+
group_update: UserGroupUpdate,
|
|
121
|
+
user: UserOAuth = Depends(current_superuser_act),
|
|
122
|
+
db: AsyncSession = Depends(get_async_db),
|
|
123
|
+
) -> UserGroupRead:
|
|
124
|
+
group = await _usergroup_or_404(group_id, db)
|
|
125
|
+
|
|
126
|
+
# Patch `viewer_paths`
|
|
127
|
+
if group_update.viewer_paths is not None:
|
|
128
|
+
group.viewer_paths = group_update.viewer_paths
|
|
129
|
+
db.add(group)
|
|
130
|
+
await db.commit()
|
|
131
|
+
|
|
132
|
+
updated_group = await _get_single_usergroup_with_user_ids(
|
|
133
|
+
group_id=group_id, db=db
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return updated_group
|
|
137
|
+
|
|
138
|
+
|
|
110
139
|
@router_group.delete("/group/{group_id}/", status_code=204)
|
|
111
140
|
async def delete_single_group(
|
|
112
141
|
group_id: int,
|
|
@@ -6,7 +6,6 @@ from .login import router_login
|
|
|
6
6
|
from .oauth import get_oauth_router
|
|
7
7
|
from .register import router_register
|
|
8
8
|
from .users import router_users
|
|
9
|
-
from .viewer_paths import router_viewer_paths
|
|
10
9
|
|
|
11
10
|
router_auth = APIRouter()
|
|
12
11
|
|
|
@@ -15,7 +14,6 @@ router_auth.include_router(router_current_user)
|
|
|
15
14
|
router_auth.include_router(router_login)
|
|
16
15
|
router_auth.include_router(router_users)
|
|
17
16
|
router_auth.include_router(router_group)
|
|
18
|
-
router_auth.include_router(router_viewer_paths)
|
|
19
17
|
router_oauth = get_oauth_router()
|
|
20
18
|
if router_oauth is not None:
|
|
21
19
|
router_auth.include_router(router_oauth)
|
|
@@ -28,7 +28,6 @@ from fractal_server.logger import set_logger
|
|
|
28
28
|
from fractal_server.syringe import Inject
|
|
29
29
|
|
|
30
30
|
from . import current_superuser_act
|
|
31
|
-
from ._aux_auth import _check_project_dirs_update
|
|
32
31
|
from ._aux_auth import _get_default_usergroup_id_or_none
|
|
33
32
|
from ._aux_auth import _get_single_user_with_groups
|
|
34
33
|
|
|
@@ -75,14 +74,6 @@ async def patch_user(
|
|
|
75
74
|
detail=f"Profile {user_update.profile_id} not found.",
|
|
76
75
|
)
|
|
77
76
|
|
|
78
|
-
if user_update.project_dirs is not None:
|
|
79
|
-
await _check_project_dirs_update(
|
|
80
|
-
old_project_dirs=user_to_patch.project_dirs,
|
|
81
|
-
new_project_dirs=user_update.project_dirs,
|
|
82
|
-
user_id=user_id,
|
|
83
|
-
db=db,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
77
|
# Modify user attributes
|
|
87
78
|
try:
|
|
88
79
|
user = await user_manager.update(
|
|
@@ -8,18 +8,12 @@ from pydantic import EmailStr
|
|
|
8
8
|
from pydantic import Field
|
|
9
9
|
|
|
10
10
|
from fractal_server.string_tools import validate_cmd
|
|
11
|
-
from fractal_server.types import
|
|
11
|
+
from fractal_server.types import AbsolutePathStr
|
|
12
12
|
from fractal_server.types import ListUniqueNonEmptyString
|
|
13
13
|
from fractal_server.types import ListUniqueNonNegativeInt
|
|
14
14
|
from fractal_server.types import NonEmptyStr
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def _validate_cmd_list(value: list[str]) -> list[str]:
|
|
18
|
-
for v in value:
|
|
19
|
-
validate_cmd(v)
|
|
20
|
-
return value
|
|
21
|
-
|
|
22
|
-
|
|
23
17
|
class OAuthAccountRead(BaseModel):
|
|
24
18
|
"""
|
|
25
19
|
Schema for storing essential `OAuthAccount` information within
|
|
@@ -44,17 +38,20 @@ class UserRead(schemas.BaseUser[int]):
|
|
|
44
38
|
group_ids_names:
|
|
45
39
|
oauth_accounts:
|
|
46
40
|
profile_id:
|
|
47
|
-
project_dirs:
|
|
48
|
-
slurm_accounts:
|
|
49
41
|
"""
|
|
50
42
|
|
|
51
43
|
group_ids_names: list[tuple[int, str]] | None = None
|
|
52
44
|
oauth_accounts: list[OAuthAccountRead]
|
|
53
45
|
profile_id: int | None = None
|
|
54
|
-
|
|
46
|
+
project_dir: str
|
|
55
47
|
slurm_accounts: list[str]
|
|
56
48
|
|
|
57
49
|
|
|
50
|
+
def _validate_cmd(value: str) -> str:
|
|
51
|
+
validate_cmd(value)
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
58
55
|
class UserUpdate(schemas.BaseUserUpdate):
|
|
59
56
|
"""
|
|
60
57
|
Schema for `User` update.
|
|
@@ -66,7 +63,7 @@ class UserUpdate(schemas.BaseUserUpdate):
|
|
|
66
63
|
is_superuser:
|
|
67
64
|
is_verified:
|
|
68
65
|
profile_id:
|
|
69
|
-
|
|
66
|
+
project_dir:
|
|
70
67
|
slurm_accounts:
|
|
71
68
|
"""
|
|
72
69
|
|
|
@@ -77,9 +74,9 @@ class UserUpdate(schemas.BaseUserUpdate):
|
|
|
77
74
|
is_superuser: bool = None
|
|
78
75
|
is_verified: bool = None
|
|
79
76
|
profile_id: int | None = None
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
project_dir: Annotated[AbsolutePathStr, AfterValidator(_validate_cmd)] = (
|
|
78
|
+
None
|
|
79
|
+
)
|
|
83
80
|
slurm_accounts: ListUniqueNonEmptyString = None
|
|
84
81
|
|
|
85
82
|
|
|
@@ -101,14 +98,10 @@ class UserCreate(schemas.BaseUserCreate):
|
|
|
101
98
|
|
|
102
99
|
Attributes:
|
|
103
100
|
profile_id:
|
|
104
|
-
project_dirs:
|
|
105
|
-
slurm_accounts:
|
|
106
101
|
"""
|
|
107
102
|
|
|
108
103
|
profile_id: int | None = None
|
|
109
|
-
|
|
110
|
-
ListUniqueAbsolutePathStr, AfterValidator(_validate_cmd_list)
|
|
111
|
-
] = Field(min_length=1)
|
|
104
|
+
project_dir: Annotated[AbsolutePathStr, AfterValidator(_validate_cmd)]
|
|
112
105
|
slurm_accounts: list[str] = Field(default_factory=list)
|
|
113
106
|
|
|
114
107
|
|
|
@@ -116,8 +109,6 @@ class UserUpdateGroups(BaseModel):
|
|
|
116
109
|
"""
|
|
117
110
|
Schema for `POST /auth/users/{user_id}/set-groups/`
|
|
118
111
|
|
|
119
|
-
Attributes:
|
|
120
|
-
group_ids:
|
|
121
112
|
"""
|
|
122
113
|
|
|
123
114
|
model_config = ConfigDict(extra="forbid")
|
|
@@ -126,14 +117,6 @@ class UserUpdateGroups(BaseModel):
|
|
|
126
117
|
|
|
127
118
|
|
|
128
119
|
class UserProfileInfo(BaseModel):
|
|
129
|
-
"""
|
|
130
|
-
Attributes:
|
|
131
|
-
has_profile:
|
|
132
|
-
resource_name:
|
|
133
|
-
profile_name:
|
|
134
|
-
username:
|
|
135
|
-
"""
|
|
136
|
-
|
|
137
120
|
has_profile: bool
|
|
138
121
|
resource_name: str | None = None
|
|
139
122
|
profile_name: str | None = None
|
|
@@ -2,13 +2,16 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
from pydantic import ConfigDict
|
|
5
|
+
from pydantic import Field
|
|
5
6
|
from pydantic import field_serializer
|
|
6
7
|
from pydantic.types import AwareDatetime
|
|
7
8
|
|
|
9
|
+
from fractal_server.types import ListUniqueAbsolutePathStr
|
|
8
10
|
from fractal_server.types import NonEmptyStr
|
|
9
11
|
|
|
10
12
|
__all__ = (
|
|
11
13
|
"UserGroupRead",
|
|
14
|
+
"UserGroupUpdate",
|
|
12
15
|
"UserGroupCreate",
|
|
13
16
|
)
|
|
14
17
|
|
|
@@ -31,6 +34,7 @@ class UserGroupRead(BaseModel):
|
|
|
31
34
|
name: str
|
|
32
35
|
timestamp_created: AwareDatetime
|
|
33
36
|
user_ids: list[int] | None = None
|
|
37
|
+
viewer_paths: list[str]
|
|
34
38
|
|
|
35
39
|
@field_serializer("timestamp_created")
|
|
36
40
|
def serialize_datetime(v: datetime) -> str:
|
|
@@ -48,3 +52,14 @@ class UserGroupCreate(BaseModel):
|
|
|
48
52
|
model_config = ConfigDict(extra="forbid")
|
|
49
53
|
|
|
50
54
|
name: NonEmptyStr
|
|
55
|
+
viewer_paths: ListUniqueAbsolutePathStr = Field(default_factory=list)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class UserGroupUpdate(BaseModel):
|
|
59
|
+
"""
|
|
60
|
+
Schema for `UserGroup` update
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(extra="forbid")
|
|
64
|
+
|
|
65
|
+
viewer_paths: ListUniqueAbsolutePathStr = None
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
from .accounting import AccountingRecordRead # noqa F401
|
|
2
|
-
from .dataset import
|
|
3
|
-
from .dataset import
|
|
4
|
-
from .dataset import
|
|
5
|
-
from .dataset import
|
|
6
|
-
from .dataset import
|
|
7
|
-
from .dumps import
|
|
8
|
-
from .dumps import
|
|
9
|
-
from .dumps import
|
|
10
|
-
from .dumps import
|
|
11
|
-
from .dumps import
|
|
12
|
-
from .dumps import
|
|
2
|
+
from .dataset import DatasetCreateV2 # noqa F401
|
|
3
|
+
from .dataset import DatasetExportV2 # noqa F401
|
|
4
|
+
from .dataset import DatasetImportV2 # noqa F401
|
|
5
|
+
from .dataset import DatasetReadV2 # noqa F401
|
|
6
|
+
from .dataset import DatasetUpdateV2 # noqa F401
|
|
7
|
+
from .dumps import DatasetDumpV2 # noqa F401
|
|
8
|
+
from .dumps import ProjectDumpV2 # noqa F401
|
|
9
|
+
from .dumps import TaskDumpV2 # noqa F401
|
|
10
|
+
from .dumps import TaskGroupDumpV2 # noqa F401
|
|
11
|
+
from .dumps import WorkflowDumpV2 # noqa F401
|
|
12
|
+
from .dumps import WorkflowTaskDumpV2 # noqa F401
|
|
13
13
|
from .history import HistoryRunRead # noqa F401
|
|
14
14
|
from .history import HistoryRunReadAggregated # noqa F401
|
|
15
15
|
from .history import HistoryUnitRead # noqa F401
|
|
16
16
|
from .history import HistoryUnitStatus # noqa F401
|
|
17
17
|
from .history import HistoryUnitStatusWithUnset # noqa F401
|
|
18
18
|
from .history import ImageLogsRequest # noqa F401
|
|
19
|
-
from .job import
|
|
20
|
-
from .job import
|
|
21
|
-
from .job import
|
|
22
|
-
from .job import
|
|
19
|
+
from .job import JobCreateV2 # noqa F401
|
|
20
|
+
from .job import JobReadV2 # noqa F401
|
|
21
|
+
from .job import JobStatusTypeV2 # noqa F401
|
|
22
|
+
from .job import JobUpdateV2 # noqa F401
|
|
23
23
|
from .manifest import ManifestV2 # noqa F401
|
|
24
24
|
from .manifest import TaskManifestV2 # noqa F401
|
|
25
25
|
from .profile import ProfileCreate # noqa F401
|
|
@@ -27,9 +27,9 @@ from .profile import ProfileRead # noqa F401
|
|
|
27
27
|
from .profile import ValidProfileLocal # noqa F401
|
|
28
28
|
from .profile import ValidProfileSlurmSSH # noqa F401
|
|
29
29
|
from .profile import ValidProfileSlurmSudo # noqa F401
|
|
30
|
-
from .project import
|
|
31
|
-
from .project import
|
|
32
|
-
from .project import
|
|
30
|
+
from .project import ProjectCreateV2 # noqa F401
|
|
31
|
+
from .project import ProjectReadV2 # noqa F401
|
|
32
|
+
from .project import ProjectUpdateV2 # noqa F401
|
|
33
33
|
from .sharing import ProjectPermissions # noqa F401
|
|
34
34
|
from .sharing import ProjectGuestCreate # noqa F401
|
|
35
35
|
from .sharing import ProjectAccessRead # noqa F401
|
|
@@ -43,36 +43,36 @@ from .resource import ResourceType # noqa F401
|
|
|
43
43
|
from .resource import ValidResourceLocal # noqa F401
|
|
44
44
|
from .resource import ValidResourceSlurmSSH # noqa F401
|
|
45
45
|
from .resource import ValidResourceSlurmSudo # noqa F401
|
|
46
|
-
from .status_legacy import
|
|
47
|
-
from .task import
|
|
48
|
-
from .task import
|
|
49
|
-
from .task import
|
|
50
|
-
from .task import
|
|
51
|
-
from .task import
|
|
46
|
+
from .status_legacy import WorkflowTaskStatusTypeV2 # noqa F401
|
|
47
|
+
from .task import TaskCreateV2 # noqa F401
|
|
48
|
+
from .task import TaskExportV2 # noqa F401
|
|
49
|
+
from .task import TaskImportV2 # noqa F401
|
|
50
|
+
from .task import TaskImportV2Legacy # noqa F401
|
|
51
|
+
from .task import TaskReadV2 # noqa F401
|
|
52
52
|
from .task import TaskType # noqa F401
|
|
53
|
-
from .task import
|
|
53
|
+
from .task import TaskUpdateV2 # noqa F401
|
|
54
54
|
from .task_collection import FractalUploadedFile # noqa F401
|
|
55
|
-
from .task_collection import
|
|
56
|
-
from .task_collection import
|
|
57
|
-
from .task_group import
|
|
58
|
-
from .task_group import
|
|
59
|
-
from .task_group import
|
|
60
|
-
from .task_group import
|
|
61
|
-
from .task_group import
|
|
55
|
+
from .task_collection import TaskCollectCustomV2 # noqa F401
|
|
56
|
+
from .task_collection import TaskCollectPipV2 # noqa F401
|
|
57
|
+
from .task_group import TaskGroupActivityActionV2 # noqa F401
|
|
58
|
+
from .task_group import TaskGroupActivityStatusV2 # noqa F401
|
|
59
|
+
from .task_group import TaskGroupActivityV2Read # noqa F401
|
|
60
|
+
from .task_group import TaskGroupCreateV2 # noqa F401
|
|
61
|
+
from .task_group import TaskGroupCreateV2Strict # noqa F401
|
|
62
62
|
from .task_group import TaskGroupReadSuperuser # noqa F401
|
|
63
|
-
from .task_group import
|
|
64
|
-
from .task_group import
|
|
65
|
-
from .task_group import
|
|
66
|
-
from .workflow import
|
|
67
|
-
from .workflow import
|
|
68
|
-
from .workflow import
|
|
69
|
-
from .workflow import
|
|
70
|
-
from .workflow import
|
|
71
|
-
from .workflow import
|
|
72
|
-
from .workflowtask import
|
|
73
|
-
from .workflowtask import
|
|
74
|
-
from .workflowtask import
|
|
75
|
-
from .workflowtask import
|
|
76
|
-
from .workflowtask import
|
|
77
|
-
from .workflowtask import
|
|
78
|
-
from .workflowtask import
|
|
63
|
+
from .task_group import TaskGroupReadV2 # noqa F401
|
|
64
|
+
from .task_group import TaskGroupUpdateV2 # noqa F401
|
|
65
|
+
from .task_group import TaskGroupV2OriginEnum # noqa F401
|
|
66
|
+
from .workflow import WorkflowCreateV2 # noqa F401
|
|
67
|
+
from .workflow import WorkflowExportV2 # noqa F401
|
|
68
|
+
from .workflow import WorkflowImportV2 # noqa F401
|
|
69
|
+
from .workflow import WorkflowReadV2 # noqa F401
|
|
70
|
+
from .workflow import WorkflowReadV2WithWarnings # noqa F401
|
|
71
|
+
from .workflow import WorkflowUpdateV2 # noqa F401
|
|
72
|
+
from .workflowtask import WorkflowTaskCreateV2 # noqa F401
|
|
73
|
+
from .workflowtask import WorkflowTaskExportV2 # noqa F401
|
|
74
|
+
from .workflowtask import WorkflowTaskImportV2 # noqa F401
|
|
75
|
+
from .workflowtask import WorkflowTaskReadV2 # noqa F401
|
|
76
|
+
from .workflowtask import WorkflowTaskReadV2WithWarning # noqa F401
|
|
77
|
+
from .workflowtask import WorkflowTaskReplaceV2 # noqa F401
|
|
78
|
+
from .workflowtask import WorkflowTaskUpdateV2 # noqa F401
|
|
@@ -1,49 +1,35 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from pathlib import Path
|
|
3
2
|
|
|
4
3
|
from pydantic import BaseModel
|
|
5
4
|
from pydantic import ConfigDict
|
|
6
5
|
from pydantic import Field
|
|
7
6
|
from pydantic import field_serializer
|
|
8
|
-
from pydantic import model_validator
|
|
9
7
|
from pydantic.types import AwareDatetime
|
|
10
8
|
|
|
11
|
-
from fractal_server.app.schemas.v2.project import
|
|
9
|
+
from fractal_server.app.schemas.v2.project import ProjectReadV2
|
|
12
10
|
from fractal_server.images import SingleImage
|
|
13
|
-
from fractal_server.types import AbsolutePathStr
|
|
14
11
|
from fractal_server.types import NonEmptyStr
|
|
15
|
-
from fractal_server.types import RelativePathStr
|
|
16
12
|
from fractal_server.types import ZarrDirStr
|
|
17
13
|
|
|
18
14
|
|
|
19
|
-
class
|
|
15
|
+
class DatasetCreateV2(BaseModel):
|
|
20
16
|
"""
|
|
21
|
-
|
|
17
|
+
DatasetCreateV2
|
|
22
18
|
|
|
23
19
|
Attributes:
|
|
24
20
|
name:
|
|
25
|
-
|
|
26
|
-
zarr_subfolder:
|
|
21
|
+
zarr_dir:
|
|
27
22
|
"""
|
|
28
23
|
|
|
29
24
|
model_config = ConfigDict(extra="forbid")
|
|
30
25
|
|
|
31
26
|
name: NonEmptyStr
|
|
32
|
-
|
|
33
|
-
zarr_subfolder: RelativePathStr | None = None
|
|
34
|
-
|
|
35
|
-
@model_validator(mode="after")
|
|
36
|
-
def validate_zarr_dir(self):
|
|
37
|
-
if (self.project_dir is None) and (self.zarr_subfolder is not None):
|
|
38
|
-
raise ValueError(
|
|
39
|
-
"Cannot provide `zarr_subfolder` without `project_dir`"
|
|
40
|
-
)
|
|
41
|
-
return self
|
|
27
|
+
zarr_dir: ZarrDirStr | None = None
|
|
42
28
|
|
|
43
29
|
|
|
44
|
-
class
|
|
30
|
+
class DatasetReadV2(BaseModel):
|
|
45
31
|
"""
|
|
46
|
-
|
|
32
|
+
DatasetReadV2
|
|
47
33
|
|
|
48
34
|
Attributes:
|
|
49
35
|
id:
|
|
@@ -58,7 +44,7 @@ class DatasetRead(BaseModel):
|
|
|
58
44
|
name: str
|
|
59
45
|
|
|
60
46
|
project_id: int
|
|
61
|
-
project:
|
|
47
|
+
project: ProjectReadV2
|
|
62
48
|
|
|
63
49
|
timestamp_created: AwareDatetime
|
|
64
50
|
|
|
@@ -69,9 +55,9 @@ class DatasetRead(BaseModel):
|
|
|
69
55
|
return v.isoformat()
|
|
70
56
|
|
|
71
57
|
|
|
72
|
-
class
|
|
58
|
+
class DatasetUpdateV2(BaseModel):
|
|
73
59
|
"""
|
|
74
|
-
|
|
60
|
+
DatasetUpdateV2
|
|
75
61
|
|
|
76
62
|
Attributes:
|
|
77
63
|
name:
|
|
@@ -81,9 +67,10 @@ class DatasetUpdate(BaseModel):
|
|
|
81
67
|
model_config = ConfigDict(extra="forbid")
|
|
82
68
|
|
|
83
69
|
name: NonEmptyStr = None
|
|
70
|
+
zarr_dir: ZarrDirStr | None = None
|
|
84
71
|
|
|
85
72
|
|
|
86
|
-
class
|
|
73
|
+
class DatasetImportV2(BaseModel):
|
|
87
74
|
"""
|
|
88
75
|
Class for `Dataset` import.
|
|
89
76
|
|
|
@@ -101,17 +88,8 @@ class DatasetImport(BaseModel):
|
|
|
101
88
|
zarr_dir: ZarrDirStr
|
|
102
89
|
images: list[SingleImage] = Field(default_factory=list)
|
|
103
90
|
|
|
104
|
-
@model_validator(mode="after")
|
|
105
|
-
def validate_image_zarr_url(self):
|
|
106
|
-
for image in self.images:
|
|
107
|
-
if not Path(image.zarr_url).is_relative_to(self.zarr_dir):
|
|
108
|
-
raise ValueError(
|
|
109
|
-
f"{image.zarr_url=} is not relative to {self.zarr_dir=}."
|
|
110
|
-
)
|
|
111
|
-
return self
|
|
112
|
-
|
|
113
91
|
|
|
114
|
-
class
|
|
92
|
+
class DatasetExportV2(BaseModel):
|
|
115
93
|
"""
|
|
116
94
|
Class for `Dataset` export.
|
|
117
95
|
|