fractal-server 2.17.0a3__py3-none-any.whl → 2.17.0a5__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 (65) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +137 -120
  3. fractal_server/app/models/security.py +19 -21
  4. fractal_server/app/models/user_settings.py +1 -0
  5. fractal_server/app/models/v2/task_group.py +1 -0
  6. fractal_server/app/routes/admin/v2/accounting.py +3 -3
  7. fractal_server/app/routes/admin/v2/impersonate.py +2 -2
  8. fractal_server/app/routes/admin/v2/job.py +6 -6
  9. fractal_server/app/routes/admin/v2/profile.py +4 -4
  10. fractal_server/app/routes/admin/v2/project.py +2 -2
  11. fractal_server/app/routes/admin/v2/resource.py +42 -8
  12. fractal_server/app/routes/admin/v2/task.py +2 -2
  13. fractal_server/app/routes/admin/v2/task_group.py +5 -5
  14. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +4 -4
  15. fractal_server/app/routes/api/__init__.py +5 -5
  16. fractal_server/app/routes/api/v2/dataset.py +10 -19
  17. fractal_server/app/routes/api/v2/history.py +8 -8
  18. fractal_server/app/routes/api/v2/images.py +5 -5
  19. fractal_server/app/routes/api/v2/job.py +8 -8
  20. fractal_server/app/routes/api/v2/pre_submission_checks.py +3 -3
  21. fractal_server/app/routes/api/v2/project.py +6 -6
  22. fractal_server/app/routes/api/v2/status_legacy.py +2 -2
  23. fractal_server/app/routes/api/v2/submit.py +24 -26
  24. fractal_server/app/routes/api/v2/task.py +6 -7
  25. fractal_server/app/routes/api/v2/task_collection.py +4 -3
  26. fractal_server/app/routes/api/v2/task_collection_custom.py +4 -3
  27. fractal_server/app/routes/api/v2/task_collection_pixi.py +2 -2
  28. fractal_server/app/routes/api/v2/task_group.py +6 -6
  29. fractal_server/app/routes/api/v2/task_group_lifecycle.py +4 -4
  30. fractal_server/app/routes/api/v2/task_version_update.py +3 -3
  31. fractal_server/app/routes/api/v2/workflow.py +9 -9
  32. fractal_server/app/routes/api/v2/workflow_import.py +2 -2
  33. fractal_server/app/routes/api/v2/workflowtask.py +5 -5
  34. fractal_server/app/routes/auth/__init__.py +34 -5
  35. fractal_server/app/routes/auth/current_user.py +22 -67
  36. fractal_server/app/routes/auth/group.py +8 -35
  37. fractal_server/app/routes/auth/register.py +2 -2
  38. fractal_server/app/routes/auth/users.py +5 -46
  39. fractal_server/app/schemas/__init__.py +0 -1
  40. fractal_server/app/schemas/user.py +23 -0
  41. fractal_server/app/schemas/v2/task_group.py +1 -0
  42. fractal_server/app/security/__init__.py +134 -46
  43. fractal_server/app/security/signup_email.py +52 -34
  44. fractal_server/config/__init__.py +1 -7
  45. fractal_server/config/_email.py +10 -47
  46. fractal_server/config/_main.py +14 -3
  47. fractal_server/migrations/versions/f65ee53991e3_user_settings_related.py +67 -0
  48. fractal_server/runner/config/_slurm.py +3 -2
  49. fractal_server/runner/executors/slurm_common/base_slurm_runner.py +2 -2
  50. fractal_server/runner/executors/slurm_common/get_slurm_config.py +1 -1
  51. fractal_server/runner/executors/slurm_common/slurm_config.py +7 -13
  52. fractal_server/runner/executors/slurm_ssh/runner.py +1 -1
  53. fractal_server/runner/executors/slurm_sudo/runner.py +1 -1
  54. fractal_server/runner/v2/_slurm_ssh.py +2 -1
  55. fractal_server/runner/v2/_slurm_sudo.py +1 -1
  56. fractal_server/runner/v2/submit_workflow.py +12 -12
  57. {fractal_server-2.17.0a3.dist-info → fractal_server-2.17.0a5.dist-info}/METADATA +1 -2
  58. {fractal_server-2.17.0a3.dist-info → fractal_server-2.17.0a5.dist-info}/RECORD +61 -64
  59. fractal_server/app/routes/aux/validate_user_settings.py +0 -76
  60. fractal_server/app/schemas/user_settings.py +0 -63
  61. fractal_server/app/user_settings.py +0 -32
  62. fractal_server/config/_init_data.py +0 -27
  63. {fractal_server-2.17.0a3.dist-info → fractal_server-2.17.0a5.dist-info}/WHEEL +0 -0
  64. {fractal_server-2.17.0a3.dist-info → fractal_server-2.17.0a5.dist-info}/entry_points.txt +0 -0
  65. {fractal_server-2.17.0a3.dist-info → fractal_server-2.17.0a5.dist-info}/licenses/LICENSE +0 -0
@@ -15,23 +15,18 @@ from fractal_server.app.models import Profile
15
15
  from fractal_server.app.models import Resource
16
16
  from fractal_server.app.models import UserGroup
17
17
  from fractal_server.app.models import UserOAuth
18
- from fractal_server.app.models import UserSettings
19
- from fractal_server.app.routes.auth import current_active_user
18
+ from fractal_server.app.routes.auth import current_user_act
20
19
  from fractal_server.app.routes.auth._aux_auth import (
21
20
  _get_single_user_with_groups,
22
21
  )
23
- from fractal_server.app.routes.aux.validate_user_settings import (
24
- verify_user_has_settings,
25
- )
26
22
  from fractal_server.app.schemas import UserProfileInfo
27
- from fractal_server.app.schemas import UserSettingsReadStrict
28
- from fractal_server.app.schemas import UserSettingsUpdateStrict
29
23
  from fractal_server.app.schemas.user import UserRead
30
24
  from fractal_server.app.schemas.user import UserUpdate
31
25
  from fractal_server.app.schemas.user import UserUpdateStrict
32
26
  from fractal_server.app.security import get_user_manager
33
27
  from fractal_server.app.security import UserManager
34
28
  from fractal_server.config import get_settings
29
+ from fractal_server.config import ViewerAuthScheme
35
30
  from fractal_server.syringe import Inject
36
31
 
37
32
  router_current_user = APIRouter()
@@ -40,7 +35,7 @@ router_current_user = APIRouter()
40
35
  @router_current_user.get("/current-user/", response_model=UserRead)
41
36
  async def get_current_user(
42
37
  group_ids_names: bool = False,
43
- user: UserOAuth = Depends(current_active_user),
38
+ user: UserOAuth = Depends(current_user_act),
44
39
  db: AsyncSession = Depends(get_async_db),
45
40
  ):
46
41
  """
@@ -56,7 +51,7 @@ async def get_current_user(
56
51
  @router_current_user.patch("/current-user/", response_model=UserRead)
57
52
  async def patch_current_user(
58
53
  user_update: UserUpdateStrict,
59
- current_user: UserOAuth = Depends(current_active_user),
54
+ current_user: UserOAuth = Depends(current_user_act),
60
55
  user_manager: UserManager = Depends(get_user_manager),
61
56
  db: AsyncSession = Depends(get_async_db),
62
57
  ):
@@ -82,47 +77,12 @@ async def patch_current_user(
82
77
  return patched_user_with_groups
83
78
 
84
79
 
85
- @router_current_user.get(
86
- "/current-user/settings/", response_model=UserSettingsReadStrict
87
- )
88
- async def get_current_user_settings(
89
- current_user: UserOAuth = Depends(current_active_user),
90
- db: AsyncSession = Depends(get_async_db),
91
- ) -> UserSettingsReadStrict:
92
- verify_user_has_settings(current_user)
93
- user_settings = await db.get(UserSettings, current_user.user_settings_id)
94
- return user_settings
95
-
96
-
97
- @router_current_user.patch(
98
- "/current-user/settings/", response_model=UserSettingsReadStrict
99
- )
100
- async def patch_current_user_settings(
101
- settings_update: UserSettingsUpdateStrict,
102
- current_user: UserOAuth = Depends(current_active_user),
103
- db: AsyncSession = Depends(get_async_db),
104
- ) -> UserSettingsReadStrict:
105
- verify_user_has_settings(current_user)
106
- current_user_settings = await db.get(
107
- UserSettings, current_user.user_settings_id
108
- )
109
-
110
- for k, v in settings_update.model_dump(exclude_unset=True).items():
111
- setattr(current_user_settings, k, v)
112
-
113
- db.add(current_user_settings)
114
- await db.commit()
115
- await db.refresh(current_user_settings)
116
-
117
- return current_user_settings
118
-
119
-
120
80
  @router_current_user.get(
121
81
  "/current-user/profile-info/",
122
82
  response_model=UserProfileInfo,
123
83
  )
124
84
  async def get_current_user_profile_info(
125
- current_user: UserOAuth = Depends(current_active_user),
85
+ current_user: UserOAuth = Depends(current_user_act),
126
86
  db: AsyncSession = Depends(get_async_db),
127
87
  ) -> UserProfileInfo:
128
88
  stm = (
@@ -152,7 +112,7 @@ async def get_current_user_profile_info(
152
112
  "/current-user/allowed-viewer-paths/", response_model=list[str]
153
113
  )
154
114
  async def get_current_user_allowed_viewer_paths(
155
- current_user: UserOAuth = Depends(current_active_user),
115
+ current_user: UserOAuth = Depends(current_user_act),
156
116
  db: AsyncSession = Depends(get_async_db),
157
117
  ) -> list[str]:
158
118
  """
@@ -162,35 +122,31 @@ async def get_current_user_allowed_viewer_paths(
162
122
 
163
123
  settings = Inject(get_settings)
164
124
 
165
- if settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "none":
166
- return []
167
-
168
125
  authorized_paths = []
169
126
 
170
- # Respond with 422 error if user has no settings
171
- verify_user_has_settings(current_user)
127
+ if settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == ViewerAuthScheme.NONE:
128
+ return authorized_paths
172
129
 
173
- # Load current user settings
174
- current_user_settings = await db.get(
175
- UserSettings, current_user.user_settings_id
176
- )
177
- # If project_dir is set, append it to the list of authorized paths
178
- if current_user_settings.project_dir is not None:
179
- authorized_paths.append(current_user_settings.project_dir)
130
+ # Append `project_dir` to the list of authorized paths
131
+ authorized_paths.append(current_user.project_dir)
180
132
 
181
133
  # If auth scheme is "users-folders" and `slurm_user` is set,
182
134
  # build and append the user folder
183
135
  if (
184
- settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "users-folders"
185
- and current_user_settings.slurm_user is not None
136
+ settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME
137
+ == ViewerAuthScheme.USERS_FOLDERS
138
+ and current_user.profile_id is not None
186
139
  ):
187
- base_folder = settings.FRACTAL_VIEWER_BASE_FOLDER
188
- user_folder = os.path.join(
189
- base_folder, current_user_settings.slurm_user
190
- )
191
- authorized_paths.append(user_folder)
140
+ profile = await db.get(Profile, current_user.profile_id)
141
+ if profile is not None and profile.username is not None:
142
+ base_folder = settings.FRACTAL_VIEWER_BASE_FOLDER
143
+ user_folder = os.path.join(base_folder, profile.username)
144
+ authorized_paths.append(user_folder)
192
145
 
193
- if settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "viewer-paths":
146
+ if (
147
+ settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME
148
+ == ViewerAuthScheme.VIEWER_PATHS
149
+ ):
194
150
  # Returns the union of `viewer_paths` for all user's groups
195
151
  cmd = (
196
152
  select(UserGroup.viewer_paths)
@@ -207,7 +163,6 @@ async def get_current_user_allowed_viewer_paths(
207
163
  for _viewer_paths in viewer_paths_nested
208
164
  for path in _viewer_paths
209
165
  }
210
-
211
166
  authorized_paths.extend(all_viewer_paths_set)
212
167
 
213
168
  return authorized_paths
@@ -9,7 +9,7 @@ from fastapi import status
9
9
  from sqlalchemy.ext.asyncio import AsyncSession
10
10
  from sqlmodel import select
11
11
 
12
- from . import current_active_superuser
12
+ from . import current_superuser_act
13
13
  from ._aux_auth import _get_default_usergroup_id
14
14
  from ._aux_auth import _get_single_usergroup_with_user_ids
15
15
  from ._aux_auth import _user_or_404
@@ -18,11 +18,9 @@ from fractal_server.app.db import get_async_db
18
18
  from fractal_server.app.models import LinkUserGroup
19
19
  from fractal_server.app.models import UserGroup
20
20
  from fractal_server.app.models import UserOAuth
21
- from fractal_server.app.models import UserSettings
22
21
  from fractal_server.app.schemas.user_group import UserGroupCreate
23
22
  from fractal_server.app.schemas.user_group import UserGroupRead
24
23
  from fractal_server.app.schemas.user_group import UserGroupUpdate
25
- from fractal_server.app.schemas.user_settings import UserSettingsUpdate
26
24
  from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
27
25
  from fractal_server.logger import set_logger
28
26
 
@@ -36,7 +34,7 @@ router_group = APIRouter()
36
34
  )
37
35
  async def get_list_user_groups(
38
36
  user_ids: bool = False,
39
- user: UserOAuth = Depends(current_active_superuser),
37
+ user: UserOAuth = Depends(current_superuser_act),
40
38
  db: AsyncSession = Depends(get_async_db),
41
39
  ) -> list[UserGroupRead]:
42
40
  # Get all groups
@@ -70,7 +68,7 @@ async def get_list_user_groups(
70
68
  )
71
69
  async def get_single_user_group(
72
70
  group_id: int,
73
- user: UserOAuth = Depends(current_active_superuser),
71
+ user: UserOAuth = Depends(current_superuser_act),
74
72
  db: AsyncSession = Depends(get_async_db),
75
73
  ) -> UserGroupRead:
76
74
  group = await _get_single_usergroup_with_user_ids(group_id=group_id, db=db)
@@ -84,7 +82,7 @@ async def get_single_user_group(
84
82
  )
85
83
  async def create_single_group(
86
84
  group_create: UserGroupCreate,
87
- user: UserOAuth = Depends(current_active_superuser),
85
+ user: UserOAuth = Depends(current_superuser_act),
88
86
  db: AsyncSession = Depends(get_async_db),
89
87
  ) -> UserGroupRead:
90
88
  # Check that name is not already in use
@@ -116,7 +114,7 @@ async def create_single_group(
116
114
  async def update_single_group(
117
115
  group_id: int,
118
116
  group_update: UserGroupUpdate,
119
- user: UserOAuth = Depends(current_active_superuser),
117
+ user: UserOAuth = Depends(current_superuser_act),
120
118
  db: AsyncSession = Depends(get_async_db),
121
119
  ) -> UserGroupRead:
122
120
  group = await _usergroup_or_404(group_id, db)
@@ -137,7 +135,7 @@ async def update_single_group(
137
135
  @router_group.delete("/group/{group_id}/", status_code=204)
138
136
  async def delete_single_group(
139
137
  group_id: int,
140
- user: UserOAuth = Depends(current_active_superuser),
138
+ user: UserOAuth = Depends(current_superuser_act),
141
139
  db: AsyncSession = Depends(get_async_db),
142
140
  ) -> Response:
143
141
  group = await _usergroup_or_404(group_id, db)
@@ -159,36 +157,11 @@ async def delete_single_group(
159
157
  return Response(status_code=status.HTTP_204_NO_CONTENT)
160
158
 
161
159
 
162
- @router_group.patch("/group/{group_id}/user-settings/", status_code=200)
163
- async def patch_user_settings_bulk(
164
- group_id: int,
165
- settings_update: UserSettingsUpdate,
166
- superuser: UserOAuth = Depends(current_active_superuser),
167
- db: AsyncSession = Depends(get_async_db),
168
- ):
169
- await _usergroup_or_404(group_id, db)
170
- res = await db.execute(
171
- select(UserSettings)
172
- .join(UserOAuth)
173
- .where(LinkUserGroup.user_id == UserOAuth.id)
174
- .where(LinkUserGroup.group_id == group_id)
175
- )
176
- settings_list = res.scalars().all()
177
- update = settings_update.model_dump(exclude_unset=True)
178
- for settings in settings_list:
179
- for k, v in update.items():
180
- setattr(settings, k, v)
181
- db.add(settings)
182
- await db.commit()
183
-
184
- return Response(status_code=status.HTTP_200_OK)
185
-
186
-
187
160
  @router_group.post("/group/{group_id}/add-user/{user_id}/", status_code=200)
188
161
  async def add_user_to_group(
189
162
  group_id: int,
190
163
  user_id: int,
191
- superuser: UserOAuth = Depends(current_active_superuser),
164
+ superuser: UserOAuth = Depends(current_superuser_act),
192
165
  db: AsyncSession = Depends(get_async_db),
193
166
  ) -> UserGroupRead:
194
167
  await _usergroup_or_404(group_id, db)
@@ -212,7 +185,7 @@ async def add_user_to_group(
212
185
  async def remove_user_from_group(
213
186
  group_id: int,
214
187
  user_id: int,
215
- superuser: UserOAuth = Depends(current_active_superuser),
188
+ superuser: UserOAuth = Depends(current_superuser_act),
216
189
  db: AsyncSession = Depends(get_async_db),
217
190
  ) -> UserGroupRead:
218
191
  # Check that user and group exist
@@ -4,7 +4,7 @@ Definition of `/auth/register/` routes.
4
4
  from fastapi import APIRouter
5
5
  from fastapi import Depends
6
6
 
7
- from . import current_active_superuser
7
+ from . import current_superuser_act
8
8
  from . import fastapi_users
9
9
  from ...schemas.user import UserCreate
10
10
  from ...schemas.user import UserRead
@@ -13,7 +13,7 @@ router_register = APIRouter()
13
13
 
14
14
  router_register.include_router(
15
15
  fastapi_users.get_register_router(UserRead, UserCreate),
16
- dependencies=[Depends(current_active_superuser)],
16
+ dependencies=[Depends(current_superuser_act)],
17
17
  )
18
18
 
19
19
 
@@ -12,22 +12,18 @@ from sqlalchemy.ext.asyncio import AsyncSession
12
12
  from sqlmodel import func
13
13
  from sqlmodel import select
14
14
 
15
- from . import current_active_superuser
15
+ from . import current_superuser_act
16
16
  from ...db import get_async_db
17
17
  from ...schemas.user import UserRead
18
18
  from ...schemas.user import UserUpdate
19
- from ..aux.validate_user_settings import verify_user_has_settings
20
19
  from ._aux_auth import _get_default_usergroup_id
21
20
  from ._aux_auth import _get_single_user_with_groups
22
21
  from ._aux_auth import FRACTAL_DEFAULT_GROUP_NAME
23
22
  from fractal_server.app.models import LinkUserGroup
24
23
  from fractal_server.app.models import UserGroup
25
24
  from fractal_server.app.models import UserOAuth
26
- from fractal_server.app.models import UserSettings
27
25
  from fractal_server.app.models.v2 import Profile
28
26
  from fractal_server.app.routes.auth._aux_auth import _user_or_404
29
- from fractal_server.app.schemas import UserSettingsRead
30
- from fractal_server.app.schemas import UserSettingsUpdate
31
27
  from fractal_server.app.schemas.user import UserUpdateGroups
32
28
  from fractal_server.app.security import get_user_manager
33
29
  from fractal_server.app.security import UserManager
@@ -43,7 +39,7 @@ logger = set_logger(__name__)
43
39
  async def get_user(
44
40
  user_id: int,
45
41
  group_ids_names: bool = True,
46
- superuser: UserOAuth = Depends(current_active_superuser),
42
+ superuser: UserOAuth = Depends(current_superuser_act),
47
43
  db: AsyncSession = Depends(get_async_db),
48
44
  ) -> UserRead:
49
45
  user = await _user_or_404(user_id, db)
@@ -57,7 +53,7 @@ async def get_user(
57
53
  async def patch_user(
58
54
  user_id: int,
59
55
  user_update: UserUpdate,
60
- current_superuser: UserOAuth = Depends(current_active_superuser),
56
+ current_superuser: UserOAuth = Depends(current_superuser_act),
61
57
  user_manager: UserManager = Depends(get_user_manager),
62
58
  db: AsyncSession = Depends(get_async_db),
63
59
  ):
@@ -113,7 +109,7 @@ async def patch_user(
113
109
  @router_users.get("/users/", response_model=list[UserRead])
114
110
  async def list_users(
115
111
  profile_id: int | None = None,
116
- user: UserOAuth = Depends(current_active_superuser),
112
+ user: UserOAuth = Depends(current_superuser_act),
117
113
  db: AsyncSession = Depends(get_async_db),
118
114
  ):
119
115
  """
@@ -148,7 +144,7 @@ async def list_users(
148
144
  async def set_user_groups(
149
145
  user_id: int,
150
146
  user_update: UserUpdateGroups,
151
- superuser: UserOAuth = Depends(current_active_superuser),
147
+ superuser: UserOAuth = Depends(current_superuser_act),
152
148
  db: AsyncSession = Depends(get_async_db),
153
149
  ) -> UserRead:
154
150
  # Preliminary check that all objects exist in the db
@@ -210,40 +206,3 @@ async def set_user_groups(
210
206
  user_with_groups = await _get_single_user_with_groups(user, db)
211
207
 
212
208
  return user_with_groups
213
-
214
-
215
- @router_users.get(
216
- "/users/{user_id}/settings/", response_model=UserSettingsRead
217
- )
218
- async def get_user_settings(
219
- user_id: int,
220
- superuser: UserOAuth = Depends(current_active_superuser),
221
- db: AsyncSession = Depends(get_async_db),
222
- ) -> UserSettingsRead:
223
- user = await _user_or_404(user_id=user_id, db=db)
224
- verify_user_has_settings(user)
225
- user_settings = await db.get(UserSettings, user.user_settings_id)
226
- return user_settings
227
-
228
-
229
- @router_users.patch(
230
- "/users/{user_id}/settings/", response_model=UserSettingsRead
231
- )
232
- async def patch_user_settings(
233
- user_id: int,
234
- settings_update: UserSettingsUpdate,
235
- superuser: UserOAuth = Depends(current_active_superuser),
236
- db: AsyncSession = Depends(get_async_db),
237
- ) -> UserSettingsRead:
238
- user = await _user_or_404(user_id=user_id, db=db)
239
- verify_user_has_settings(user)
240
- user_settings = await db.get(UserSettings, user.user_settings_id)
241
-
242
- for k, v in settings_update.model_dump(exclude_unset=True).items():
243
- setattr(user_settings, k, v)
244
-
245
- db.add(user_settings)
246
- await db.commit()
247
- await db.refresh(user_settings)
248
-
249
- return user_settings
@@ -1,3 +1,2 @@
1
1
  from .user import * # noqa: F401, F403
2
2
  from .user_group import * # noqa: F401, F403
3
- from .user_settings import * # noqa: F401, F403
@@ -1,9 +1,15 @@
1
+ from typing import Annotated
2
+
1
3
  from fastapi_users import schemas
4
+ from pydantic import AfterValidator
2
5
  from pydantic import BaseModel
3
6
  from pydantic import ConfigDict
4
7
  from pydantic import EmailStr
5
8
  from pydantic import Field
6
9
 
10
+ from fractal_server.string_tools import validate_cmd
11
+ from fractal_server.types import AbsolutePathStr
12
+ from fractal_server.types import ListUniqueNonEmptyString
7
13
  from fractal_server.types import ListUniqueNonNegativeInt
8
14
  from fractal_server.types import NonEmptyStr
9
15
 
@@ -37,6 +43,13 @@ class UserRead(schemas.BaseUser[int]):
37
43
  group_ids_names: list[tuple[int, str]] | None = None
38
44
  oauth_accounts: list[OAuthAccountRead]
39
45
  profile_id: int | None = None
46
+ project_dir: str
47
+ slurm_accounts: list[str]
48
+
49
+
50
+ def _validate_cmd(value: str) -> str:
51
+ validate_cmd(value)
52
+ return value
40
53
 
41
54
 
42
55
  class UserUpdate(schemas.BaseUserUpdate):
@@ -50,6 +63,8 @@ class UserUpdate(schemas.BaseUserUpdate):
50
63
  is_superuser:
51
64
  is_verified:
52
65
  profile_id:
66
+ project_dir:
67
+ slurm_accounts:
53
68
  """
54
69
 
55
70
  model_config = ConfigDict(extra="forbid")
@@ -59,6 +74,10 @@ class UserUpdate(schemas.BaseUserUpdate):
59
74
  is_superuser: bool = None
60
75
  is_verified: bool = None
61
76
  profile_id: int | None = None
77
+ project_dir: Annotated[
78
+ AbsolutePathStr, AfterValidator(_validate_cmd)
79
+ ] | None = None
80
+ slurm_accounts: ListUniqueNonEmptyString | None = None
62
81
 
63
82
 
64
83
  class UserUpdateStrict(BaseModel):
@@ -66,9 +85,11 @@ class UserUpdateStrict(BaseModel):
66
85
  Schema for `User` self-editing.
67
86
 
68
87
  Attributes:
88
+ slurm_accounts:
69
89
  """
70
90
 
71
91
  model_config = ConfigDict(extra="forbid")
92
+ slurm_accounts: ListUniqueNonEmptyString | None = None
72
93
 
73
94
 
74
95
  class UserCreate(schemas.BaseUserCreate):
@@ -80,6 +101,8 @@ class UserCreate(schemas.BaseUserCreate):
80
101
  """
81
102
 
82
103
  profile_id: int | None = None
104
+ project_dir: Annotated[AbsolutePathStr, AfterValidator(_validate_cmd)]
105
+ slurm_accounts: list[str] = Field(default_factory=list)
83
106
 
84
107
 
85
108
  class UserUpdateGroups(BaseModel):
@@ -37,6 +37,7 @@ class TaskGroupActivityActionV2(StrEnum):
37
37
  class TaskGroupCreateV2(BaseModel):
38
38
  model_config = ConfigDict(extra="forbid")
39
39
  user_id: int
40
+ resource_id: int
40
41
  user_group_id: int | None = None
41
42
  active: bool = True
42
43
  origin: TaskGroupV2OriginEnum