fractal-server 2.16.5__py3-none-any.whl → 2.17.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +178 -52
  3. fractal_server/app/db/__init__.py +9 -11
  4. fractal_server/app/models/security.py +30 -22
  5. fractal_server/app/models/user_settings.py +5 -4
  6. fractal_server/app/models/v2/__init__.py +4 -0
  7. fractal_server/app/models/v2/job.py +3 -4
  8. fractal_server/app/models/v2/profile.py +16 -0
  9. fractal_server/app/models/v2/project.py +5 -0
  10. fractal_server/app/models/v2/resource.py +130 -0
  11. fractal_server/app/models/v2/task_group.py +4 -0
  12. fractal_server/app/routes/admin/v2/__init__.py +4 -0
  13. fractal_server/app/routes/admin/v2/_aux_functions.py +55 -0
  14. fractal_server/app/routes/admin/v2/accounting.py +3 -3
  15. fractal_server/app/routes/admin/v2/impersonate.py +2 -2
  16. fractal_server/app/routes/admin/v2/job.py +51 -15
  17. fractal_server/app/routes/admin/v2/profile.py +100 -0
  18. fractal_server/app/routes/admin/v2/project.py +2 -2
  19. fractal_server/app/routes/admin/v2/resource.py +222 -0
  20. fractal_server/app/routes/admin/v2/task.py +59 -32
  21. fractal_server/app/routes/admin/v2/task_group.py +17 -12
  22. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +52 -86
  23. fractal_server/app/routes/api/__init__.py +45 -8
  24. fractal_server/app/routes/api/v2/_aux_functions.py +17 -1
  25. fractal_server/app/routes/api/v2/_aux_functions_history.py +2 -2
  26. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
  27. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +55 -19
  28. fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +21 -17
  29. fractal_server/app/routes/api/v2/dataset.py +10 -19
  30. fractal_server/app/routes/api/v2/history.py +8 -8
  31. fractal_server/app/routes/api/v2/images.py +5 -5
  32. fractal_server/app/routes/api/v2/job.py +8 -8
  33. fractal_server/app/routes/api/v2/pre_submission_checks.py +3 -3
  34. fractal_server/app/routes/api/v2/project.py +15 -7
  35. fractal_server/app/routes/api/v2/status_legacy.py +2 -2
  36. fractal_server/app/routes/api/v2/submit.py +49 -42
  37. fractal_server/app/routes/api/v2/task.py +26 -8
  38. fractal_server/app/routes/api/v2/task_collection.py +39 -50
  39. fractal_server/app/routes/api/v2/task_collection_custom.py +10 -6
  40. fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -42
  41. fractal_server/app/routes/api/v2/task_group.py +19 -9
  42. fractal_server/app/routes/api/v2/task_group_lifecycle.py +43 -86
  43. fractal_server/app/routes/api/v2/task_version_update.py +3 -3
  44. fractal_server/app/routes/api/v2/workflow.py +9 -9
  45. fractal_server/app/routes/api/v2/workflow_import.py +29 -16
  46. fractal_server/app/routes/api/v2/workflowtask.py +5 -5
  47. fractal_server/app/routes/auth/__init__.py +34 -5
  48. fractal_server/app/routes/auth/_aux_auth.py +39 -20
  49. fractal_server/app/routes/auth/current_user.py +56 -67
  50. fractal_server/app/routes/auth/group.py +29 -46
  51. fractal_server/app/routes/auth/oauth.py +55 -38
  52. fractal_server/app/routes/auth/register.py +2 -2
  53. fractal_server/app/routes/auth/router.py +4 -2
  54. fractal_server/app/routes/auth/users.py +29 -53
  55. fractal_server/app/routes/aux/_runner.py +2 -1
  56. fractal_server/app/routes/aux/validate_user_profile.py +62 -0
  57. fractal_server/app/schemas/__init__.py +0 -1
  58. fractal_server/app/schemas/user.py +43 -13
  59. fractal_server/app/schemas/user_group.py +2 -1
  60. fractal_server/app/schemas/v2/__init__.py +12 -0
  61. fractal_server/app/schemas/v2/profile.py +78 -0
  62. fractal_server/app/schemas/v2/resource.py +137 -0
  63. fractal_server/app/schemas/v2/task_collection.py +11 -3
  64. fractal_server/app/schemas/v2/task_group.py +5 -0
  65. fractal_server/app/security/__init__.py +174 -75
  66. fractal_server/app/security/signup_email.py +52 -34
  67. fractal_server/config/__init__.py +27 -0
  68. fractal_server/config/_data.py +68 -0
  69. fractal_server/config/_database.py +59 -0
  70. fractal_server/config/_email.py +133 -0
  71. fractal_server/config/_main.py +78 -0
  72. fractal_server/config/_oauth.py +69 -0
  73. fractal_server/config/_settings_config.py +7 -0
  74. fractal_server/data_migrations/2_17_0.py +339 -0
  75. fractal_server/images/tools.py +3 -3
  76. fractal_server/logger.py +3 -3
  77. fractal_server/main.py +17 -23
  78. fractal_server/migrations/naming_convention.py +1 -1
  79. fractal_server/migrations/versions/83bc2ad3ffcc_2_17_0.py +195 -0
  80. fractal_server/runner/config/__init__.py +2 -0
  81. fractal_server/runner/config/_local.py +21 -0
  82. fractal_server/runner/config/_slurm.py +129 -0
  83. fractal_server/runner/config/slurm_mem_to_MB.py +63 -0
  84. fractal_server/runner/exceptions.py +4 -0
  85. fractal_server/runner/executors/base_runner.py +17 -7
  86. fractal_server/runner/executors/local/get_local_config.py +21 -86
  87. fractal_server/runner/executors/local/runner.py +48 -5
  88. fractal_server/runner/executors/slurm_common/_batching.py +2 -2
  89. fractal_server/runner/executors/slurm_common/base_slurm_runner.py +60 -26
  90. fractal_server/runner/executors/slurm_common/get_slurm_config.py +39 -55
  91. fractal_server/runner/executors/slurm_common/remote.py +1 -1
  92. fractal_server/runner/executors/slurm_common/slurm_config.py +214 -0
  93. fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -1
  94. fractal_server/runner/executors/slurm_ssh/runner.py +12 -14
  95. fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -2
  96. fractal_server/runner/executors/slurm_sudo/runner.py +12 -12
  97. fractal_server/runner/v2/_local.py +36 -21
  98. fractal_server/runner/v2/_slurm_ssh.py +41 -4
  99. fractal_server/runner/v2/_slurm_sudo.py +42 -12
  100. fractal_server/runner/v2/db_tools.py +1 -1
  101. fractal_server/runner/v2/runner.py +3 -11
  102. fractal_server/runner/v2/runner_functions.py +42 -28
  103. fractal_server/runner/v2/submit_workflow.py +88 -109
  104. fractal_server/runner/versions.py +8 -3
  105. fractal_server/ssh/_fabric.py +6 -6
  106. fractal_server/tasks/config/__init__.py +3 -0
  107. fractal_server/tasks/config/_pixi.py +127 -0
  108. fractal_server/tasks/config/_python.py +51 -0
  109. fractal_server/tasks/v2/local/_utils.py +7 -7
  110. fractal_server/tasks/v2/local/collect.py +13 -5
  111. fractal_server/tasks/v2/local/collect_pixi.py +26 -10
  112. fractal_server/tasks/v2/local/deactivate.py +7 -1
  113. fractal_server/tasks/v2/local/deactivate_pixi.py +5 -1
  114. fractal_server/tasks/v2/local/delete.py +5 -1
  115. fractal_server/tasks/v2/local/reactivate.py +13 -5
  116. fractal_server/tasks/v2/local/reactivate_pixi.py +27 -9
  117. fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +11 -10
  118. fractal_server/tasks/v2/ssh/_utils.py +6 -7
  119. fractal_server/tasks/v2/ssh/collect.py +19 -12
  120. fractal_server/tasks/v2/ssh/collect_pixi.py +34 -16
  121. fractal_server/tasks/v2/ssh/deactivate.py +12 -8
  122. fractal_server/tasks/v2/ssh/deactivate_pixi.py +14 -10
  123. fractal_server/tasks/v2/ssh/delete.py +12 -9
  124. fractal_server/tasks/v2/ssh/reactivate.py +18 -12
  125. fractal_server/tasks/v2/ssh/reactivate_pixi.py +36 -17
  126. fractal_server/tasks/v2/templates/4_pip_show.sh +4 -6
  127. fractal_server/tasks/v2/utils_database.py +2 -2
  128. fractal_server/tasks/v2/utils_pixi.py +3 -0
  129. fractal_server/tasks/v2/utils_python_interpreter.py +8 -16
  130. fractal_server/tasks/v2/utils_templates.py +7 -10
  131. fractal_server/utils.py +1 -1
  132. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0.dist-info}/METADATA +8 -10
  133. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0.dist-info}/RECORD +137 -118
  134. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0.dist-info}/WHEEL +1 -1
  135. fractal_server/app/routes/aux/validate_user_settings.py +0 -73
  136. fractal_server/app/schemas/user_settings.py +0 -67
  137. fractal_server/app/user_settings.py +0 -42
  138. fractal_server/config.py +0 -906
  139. fractal_server/data_migrations/2_14_10.py +0 -48
  140. fractal_server/runner/executors/slurm_common/_slurm_config.py +0 -471
  141. /fractal_server/{runner → app}/shutdown.py +0 -0
  142. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0.dist-info}/entry_points.txt +0 -0
  143. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0.dist-info/licenses}/LICENSE +0 -0
@@ -15,7 +15,7 @@ from ._aux_functions import _workflow_insert_task
15
15
  from ._aux_functions_tasks import _check_type_filters_compatibility
16
16
  from ._aux_functions_tasks import _get_task_read_access
17
17
  from fractal_server.app.models import UserOAuth
18
- from fractal_server.app.routes.auth import current_active_user
18
+ from fractal_server.app.routes.auth import current_user_act_ver_prof
19
19
  from fractal_server.app.schemas.v2 import TaskType
20
20
  from fractal_server.app.schemas.v2 import WorkflowTaskCreateV2
21
21
  from fractal_server.app.schemas.v2 import WorkflowTaskReadV2
@@ -34,7 +34,7 @@ async def create_workflowtask(
34
34
  workflow_id: int,
35
35
  task_id: int,
36
36
  wftask: WorkflowTaskCreateV2,
37
- user: UserOAuth = Depends(current_active_user),
37
+ user: UserOAuth = Depends(current_user_act_ver_prof),
38
38
  db: AsyncSession = Depends(get_async_db),
39
39
  ) -> WorkflowTaskReadV2 | None:
40
40
  """
@@ -103,7 +103,7 @@ async def read_workflowtask(
103
103
  project_id: int,
104
104
  workflow_id: int,
105
105
  workflow_task_id: int,
106
- user: UserOAuth = Depends(current_active_user),
106
+ user: UserOAuth = Depends(current_user_act_ver_prof),
107
107
  db: AsyncSession = Depends(get_async_db),
108
108
  ):
109
109
  workflow_task, _ = await _get_workflow_task_check_owner(
@@ -125,7 +125,7 @@ async def update_workflowtask(
125
125
  workflow_id: int,
126
126
  workflow_task_id: int,
127
127
  workflow_task_update: WorkflowTaskUpdateV2,
128
- user: UserOAuth = Depends(current_active_user),
128
+ user: UserOAuth = Depends(current_user_act_ver_prof),
129
129
  db: AsyncSession = Depends(get_async_db),
130
130
  ) -> WorkflowTaskReadV2 | None:
131
131
  """
@@ -210,7 +210,7 @@ async def delete_workflowtask(
210
210
  project_id: int,
211
211
  workflow_id: int,
212
212
  workflow_task_id: int,
213
- user: UserOAuth = Depends(current_active_user),
213
+ user: UserOAuth = Depends(current_user_act_ver_prof),
214
214
  db: AsyncSession = Depends(get_async_db),
215
215
  ) -> Response:
216
216
  """
@@ -1,3 +1,6 @@
1
+ from fastapi import Depends
2
+ from fastapi import HTTPException
3
+ from fastapi import status
1
4
  from fastapi_users import FastAPIUsers
2
5
  from fastapi_users.authentication import AuthenticationBackend
3
6
  from fastapi_users.authentication import BearerTransport
@@ -46,10 +49,36 @@ fastapi_users = FastAPIUsers[UserOAuth, int](
46
49
  get_user_manager,
47
50
  [token_backend, cookie_backend],
48
51
  )
49
- current_active_user = fastapi_users.current_user(active=True)
50
- current_active_verified_user = fastapi_users.current_user(
51
- active=True, verified=True
52
+
53
+ # Current-user dependencies
54
+ current_user_act = fastapi_users.current_user(active=True)
55
+ current_user_act_ver = fastapi_users.current_user(
56
+ active=True,
57
+ verified=True,
52
58
  )
53
- current_active_superuser = fastapi_users.current_user(
54
- active=True, superuser=True
59
+
60
+
61
+ async def current_user_act_ver_prof(
62
+ user: UserOAuth = Depends(current_user_act_ver),
63
+ ) -> UserOAuth:
64
+ """
65
+ Require a active&verified user, with a non-null `profile_id`.
66
+
67
+ Raises 401 if user does not exist or is not active.
68
+ Raises 403 if user is not verified or has null `profile_id`.
69
+ """
70
+ if user.profile_id is None:
71
+ raise HTTPException(
72
+ status_code=status.HTTP_403_FORBIDDEN,
73
+ detail=(
74
+ f"Forbidden access "
75
+ f"({user.is_verified=} {user.profile_id=})."
76
+ ),
77
+ )
78
+ return user
79
+
80
+
81
+ current_superuser_act = fastapi_users.current_user(
82
+ active=True,
83
+ superuser=True,
55
84
  )
@@ -9,8 +9,10 @@ from fractal_server.app.models.security import UserGroup
9
9
  from fractal_server.app.models.security import UserOAuth
10
10
  from fractal_server.app.schemas.user import UserRead
11
11
  from fractal_server.app.schemas.user_group import UserGroupRead
12
- from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
12
+ from fractal_server.config import get_settings
13
13
  from fractal_server.logger import set_logger
14
+ from fractal_server.syringe import Inject
15
+
14
16
 
15
17
  logger = set_logger(__name__)
16
18
 
@@ -22,13 +24,16 @@ async def _get_single_user_with_groups(
22
24
  """
23
25
  Enrich a user object by filling its `group_ids_names` attribute.
24
26
 
25
- Arguments:
27
+ Args:
26
28
  user: The current `UserOAuth` object
27
29
  db: Async db session
28
30
 
29
31
  Returns:
30
32
  A `UserRead` object with `group_ids_names` dict
31
33
  """
34
+
35
+ settings = Inject(get_settings)
36
+
32
37
  stm_groups = (
33
38
  select(UserGroup)
34
39
  .join(LinkUserGroup)
@@ -39,25 +44,25 @@ async def _get_single_user_with_groups(
39
44
  groups = res.scalars().unique().all()
40
45
  group_ids_names = [(group.id, group.name) for group in groups]
41
46
 
42
- # Check that Fractal Default Group is the first of the list. If not, fix.
47
+ # Identify the default-group position in the list of groups
43
48
  index = next(
44
49
  (
45
- i
46
- for i, group_tuple in enumerate(group_ids_names)
47
- if group_tuple[1] == FRACTAL_DEFAULT_GROUP_NAME
50
+ ind
51
+ for ind, group_tuple in enumerate(group_ids_names)
52
+ if group_tuple[1] == settings.FRACTAL_DEFAULT_GROUP_NAME
48
53
  ),
49
54
  None,
50
55
  )
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:
56
+ if (index is None) or (index == 0):
57
+ # Either the default group does not exist, or it is already the first
58
+ # one. No action needed.
59
+ pass
60
+ else:
61
+ # Move the default group to the first position
57
62
  default_group = group_ids_names.pop(index)
58
63
  group_ids_names.insert(0, default_group)
59
- else:
60
- pass
64
+
65
+ # Create dump of `user.oauth_accounts` relationship
61
66
  oauth_accounts = [
62
67
  oauth_account.model_dump() for oauth_account in user.oauth_accounts
63
68
  ]
@@ -75,7 +80,7 @@ async def _get_single_usergroup_with_user_ids(
75
80
  """
76
81
  Get a group, and construct its `user_ids` list.
77
82
 
78
- Arguments:
83
+ Args:
79
84
  group_id:
80
85
  db:
81
86
 
@@ -98,7 +103,7 @@ async def _user_or_404(user_id: int, db: AsyncSession) -> UserOAuth:
98
103
  """
99
104
  Get a user from db, or raise a 404 HTTP exception if missing.
100
105
 
101
- Arguments:
106
+ Args:
102
107
  user_id: ID of the user
103
108
  db: Async db session
104
109
  """
@@ -121,17 +126,31 @@ async def _usergroup_or_404(usergroup_id: int, db: AsyncSession) -> UserGroup:
121
126
  return user
122
127
 
123
128
 
124
- async def _get_default_usergroup_id(db: AsyncSession) -> int:
129
+ async def _get_default_usergroup_id_or_none(db: AsyncSession) -> int | None:
130
+ """
131
+ Return the ID of the group named `"All"`, if `FRACTAL_DEFAULT_GROUP_NAME`
132
+ is set and such group exists. Return `None`, if
133
+ `FRACTAL_DEFAULT_GROUP_NAME=None` or if the `"All"` group does not exist.
134
+ """
135
+ settings = Inject(get_settings)
125
136
  stm = select(UserGroup.id).where(
126
- UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
137
+ UserGroup.name == settings.FRACTAL_DEFAULT_GROUP_NAME
127
138
  )
128
139
  res = await db.execute(stm)
129
140
  user_group_id = res.scalars().one_or_none()
130
- if user_group_id is None:
141
+
142
+ if (
143
+ settings.FRACTAL_DEFAULT_GROUP_NAME is not None
144
+ and user_group_id is None
145
+ ):
131
146
  raise HTTPException(
132
147
  status_code=status.HTTP_404_NOT_FOUND,
133
- detail=f"User group '{FRACTAL_DEFAULT_GROUP_NAME}' not found.",
148
+ detail=(
149
+ f"User group '{settings.FRACTAL_DEFAULT_GROUP_NAME}'"
150
+ " not found.",
151
+ ),
134
152
  )
153
+
135
154
  return user_group_id
136
155
 
137
156
 
@@ -5,26 +5,28 @@ import os
5
5
 
6
6
  from fastapi import APIRouter
7
7
  from fastapi import Depends
8
- from fastapi_users import schemas
9
8
  from sqlalchemy.ext.asyncio import AsyncSession
10
9
  from sqlmodel import select
11
10
 
12
- from . import current_active_user
13
- from ...db import get_async_db
14
- from ...schemas.user import UserRead
15
- from ...schemas.user import UserUpdate
16
- from ...schemas.user import UserUpdateStrict
17
- from ..aux.validate_user_settings import verify_user_has_settings
18
- from ._aux_auth import _get_single_user_with_groups
11
+ from fractal_server.app.db import get_async_db
19
12
  from fractal_server.app.models import LinkUserGroup
13
+ from fractal_server.app.models import Profile
14
+ from fractal_server.app.models import Resource
20
15
  from fractal_server.app.models import UserGroup
21
16
  from fractal_server.app.models import UserOAuth
22
- from fractal_server.app.models import UserSettings
23
- from fractal_server.app.schemas import UserSettingsReadStrict
24
- from fractal_server.app.schemas import UserSettingsUpdateStrict
17
+ from fractal_server.app.routes.auth import current_user_act
18
+ from fractal_server.app.routes.auth import current_user_act_ver
19
+ from fractal_server.app.routes.auth._aux_auth import (
20
+ _get_single_user_with_groups,
21
+ )
22
+ from fractal_server.app.schemas import UserProfileInfo
23
+ from fractal_server.app.schemas.user import UserRead
24
+ from fractal_server.app.schemas.user import UserUpdate
25
+ from fractal_server.app.schemas.user import UserUpdateStrict
25
26
  from fractal_server.app.security import get_user_manager
26
27
  from fractal_server.app.security import UserManager
27
- from fractal_server.config import get_settings
28
+ from fractal_server.config import DataAuthScheme
29
+ from fractal_server.config import get_data_settings
28
30
  from fractal_server.syringe import Inject
29
31
 
30
32
  router_current_user = APIRouter()
@@ -33,7 +35,7 @@ router_current_user = APIRouter()
33
35
  @router_current_user.get("/current-user/", response_model=UserRead)
34
36
  async def get_current_user(
35
37
  group_ids_names: bool = False,
36
- user: UserOAuth = Depends(current_active_user),
38
+ user: UserOAuth = Depends(current_user_act),
37
39
  db: AsyncSession = Depends(get_async_db),
38
40
  ):
39
41
  """
@@ -49,7 +51,7 @@ async def get_current_user(
49
51
  @router_current_user.patch("/current-user/", response_model=UserRead)
50
52
  async def patch_current_user(
51
53
  user_update: UserUpdateStrict,
52
- current_user: UserOAuth = Depends(current_active_user),
54
+ current_user: UserOAuth = Depends(current_user_act),
53
55
  user_manager: UserManager = Depends(get_user_manager),
54
56
  db: AsyncSession = Depends(get_async_db),
55
57
  ):
@@ -64,7 +66,7 @@ async def patch_current_user(
64
66
  # their own password
65
67
 
66
68
  user = await user_manager.update(update, current_user, safe=True)
67
- validated_user = schemas.model_validate(UserOAuth, user.model_dump())
69
+ validated_user = UserOAuth.model_validate(user.model_dump())
68
70
 
69
71
  patched_user = await db.get(
70
72
  UserOAuth, validated_user.id, populate_existing=True
@@ -76,83 +78,71 @@ async def patch_current_user(
76
78
 
77
79
 
78
80
  @router_current_user.get(
79
- "/current-user/settings/", response_model=UserSettingsReadStrict
81
+ "/current-user/profile-info/",
82
+ response_model=UserProfileInfo,
80
83
  )
81
- async def get_current_user_settings(
82
- current_user: UserOAuth = Depends(current_active_user),
84
+ async def get_current_user_profile_info(
85
+ current_user: UserOAuth = Depends(current_user_act),
83
86
  db: AsyncSession = Depends(get_async_db),
84
- ) -> UserSettingsReadStrict:
85
- verify_user_has_settings(current_user)
86
- user_settings = await db.get(UserSettings, current_user.user_settings_id)
87
- return user_settings
88
-
89
-
90
- @router_current_user.patch(
91
- "/current-user/settings/", response_model=UserSettingsReadStrict
92
- )
93
- async def patch_current_user_settings(
94
- settings_update: UserSettingsUpdateStrict,
95
- current_user: UserOAuth = Depends(current_active_user),
96
- db: AsyncSession = Depends(get_async_db),
97
- ) -> UserSettingsReadStrict:
98
- verify_user_has_settings(current_user)
99
- current_user_settings = await db.get(
100
- UserSettings, current_user.user_settings_id
87
+ ) -> UserProfileInfo:
88
+ stm = (
89
+ select(Resource, Profile)
90
+ .join(UserOAuth)
91
+ .where(Resource.id == Profile.resource_id)
92
+ .where(Profile.id == UserOAuth.profile_id)
93
+ .where(UserOAuth.id == current_user.id)
101
94
  )
95
+ res = await db.execute(stm)
96
+ db_data = res.one_or_none()
97
+ if db_data is None:
98
+ response_data = dict(has_profile=False)
99
+ else:
100
+ resource, profile = db_data
101
+ response_data = dict(
102
+ has_profile=True,
103
+ resource_name=resource.name,
104
+ profile_name=profile.name,
105
+ username=profile.username,
106
+ )
102
107
 
103
- for k, v in settings_update.model_dump(exclude_unset=True).items():
104
- setattr(current_user_settings, k, v)
105
-
106
- db.add(current_user_settings)
107
- await db.commit()
108
- await db.refresh(current_user_settings)
109
-
110
- return current_user_settings
108
+ return response_data
111
109
 
112
110
 
113
111
  @router_current_user.get(
114
112
  "/current-user/allowed-viewer-paths/", response_model=list[str]
115
113
  )
116
114
  async def get_current_user_allowed_viewer_paths(
117
- current_user: UserOAuth = Depends(current_active_user),
115
+ current_user: UserOAuth = Depends(current_user_act_ver),
118
116
  db: AsyncSession = Depends(get_async_db),
119
117
  ) -> list[str]:
120
118
  """
121
119
  Returns the allowed viewer paths for current user, according to the
122
- selected FRACTAL_VIEWER_AUTHORIZATION_SCHEME
120
+ selected FRACTAL_DATA_AUTH_SCHEME
123
121
  """
124
122
 
125
- settings = Inject(get_settings)
126
-
127
- if settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "none":
128
- return []
123
+ data_settings = Inject(get_data_settings)
129
124
 
130
125
  authorized_paths = []
131
126
 
132
- # Respond with 422 error if user has no settings
133
- verify_user_has_settings(current_user)
127
+ if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.NONE:
128
+ return authorized_paths
134
129
 
135
- # Load current user settings
136
- current_user_settings = await db.get(
137
- UserSettings, current_user.user_settings_id
138
- )
139
- # If project_dir is set, append it to the list of authorized paths
140
- if current_user_settings.project_dir is not None:
141
- 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)
142
132
 
143
133
  # If auth scheme is "users-folders" and `slurm_user` is set,
144
134
  # build and append the user folder
145
135
  if (
146
- settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "users-folders"
147
- and current_user_settings.slurm_user is not None
136
+ data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.USERS_FOLDERS
137
+ and current_user.profile_id is not None
148
138
  ):
149
- base_folder = settings.FRACTAL_VIEWER_BASE_FOLDER
150
- user_folder = os.path.join(
151
- base_folder, current_user_settings.slurm_user
152
- )
153
- authorized_paths.append(user_folder)
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)
154
144
 
155
- if settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "viewer-paths":
145
+ if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.VIEWER_PATHS:
156
146
  # Returns the union of `viewer_paths` for all user's groups
157
147
  cmd = (
158
148
  select(UserGroup.viewer_paths)
@@ -169,7 +159,6 @@ async def get_current_user_allowed_viewer_paths(
169
159
  for _viewer_paths in viewer_paths_nested
170
160
  for path in _viewer_paths
171
161
  }
172
-
173
162
  authorized_paths.extend(all_viewer_paths_set)
174
163
 
175
164
  return authorized_paths
@@ -9,8 +9,8 @@ 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
13
- from ._aux_auth import _get_default_usergroup_id
12
+ from . import current_superuser_act
13
+ from ._aux_auth import _get_default_usergroup_id_or_none
14
14
  from ._aux_auth import _get_single_usergroup_with_user_ids
15
15
  from ._aux_auth import _user_or_404
16
16
  from ._aux_auth import _usergroup_or_404
@@ -18,16 +18,16 @@ 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
- from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
24
+ from fractal_server.config import get_settings
27
25
  from fractal_server.logger import set_logger
26
+ from fractal_server.syringe import Inject
28
27
 
29
28
  logger = set_logger(__name__)
30
29
 
30
+
31
31
  router_group = APIRouter()
32
32
 
33
33
 
@@ -36,11 +36,11 @@ router_group = APIRouter()
36
36
  )
37
37
  async def get_list_user_groups(
38
38
  user_ids: bool = False,
39
- user: UserOAuth = Depends(current_active_superuser),
39
+ user: UserOAuth = Depends(current_superuser_act),
40
40
  db: AsyncSession = Depends(get_async_db),
41
41
  ) -> list[UserGroupRead]:
42
42
  # Get all groups
43
- stm_all_groups = select(UserGroup)
43
+ stm_all_groups = select(UserGroup).order_by(UserGroup.id)
44
44
  res = await db.execute(stm_all_groups)
45
45
  groups = res.scalars().all()
46
46
 
@@ -70,7 +70,7 @@ async def get_list_user_groups(
70
70
  )
71
71
  async def get_single_user_group(
72
72
  group_id: int,
73
- user: UserOAuth = Depends(current_active_superuser),
73
+ user: UserOAuth = Depends(current_superuser_act),
74
74
  db: AsyncSession = Depends(get_async_db),
75
75
  ) -> UserGroupRead:
76
76
  group = await _get_single_usergroup_with_user_ids(group_id=group_id, db=db)
@@ -84,7 +84,7 @@ async def get_single_user_group(
84
84
  )
85
85
  async def create_single_group(
86
86
  group_create: UserGroupCreate,
87
- user: UserOAuth = Depends(current_active_superuser),
87
+ user: UserOAuth = Depends(current_superuser_act),
88
88
  db: AsyncSession = Depends(get_async_db),
89
89
  ) -> UserGroupRead:
90
90
  # Check that name is not already in use
@@ -116,7 +116,7 @@ async def create_single_group(
116
116
  async def update_single_group(
117
117
  group_id: int,
118
118
  group_update: UserGroupUpdate,
119
- user: UserOAuth = Depends(current_active_superuser),
119
+ user: UserOAuth = Depends(current_superuser_act),
120
120
  db: AsyncSession = Depends(get_async_db),
121
121
  ) -> UserGroupRead:
122
122
  group = await _usergroup_or_404(group_id, db)
@@ -137,58 +137,38 @@ async def update_single_group(
137
137
  @router_group.delete("/group/{group_id}/", status_code=204)
138
138
  async def delete_single_group(
139
139
  group_id: int,
140
- user: UserOAuth = Depends(current_active_superuser),
140
+ user: UserOAuth = Depends(current_superuser_act),
141
141
  db: AsyncSession = Depends(get_async_db),
142
142
  ) -> Response:
143
+ """
144
+ Delete a user group.
145
+
146
+ If `FRACTAL_DEFAULT_GROUP_NAME="All"`, a group named `"All"` cannot be
147
+ deleted. If `FRACTAL_DEFAULT_GROUP_NAME=None`, any group can be deleted.
148
+ """
149
+ settings = Inject(get_settings)
143
150
  group = await _usergroup_or_404(group_id, db)
144
151
 
145
- if group.name == FRACTAL_DEFAULT_GROUP_NAME:
152
+ if group.name == settings.FRACTAL_DEFAULT_GROUP_NAME:
146
153
  raise HTTPException(
147
154
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
148
155
  detail=(
149
156
  "Cannot delete default UserGroup "
150
- f"'{FRACTAL_DEFAULT_GROUP_NAME}'."
157
+ f"'{settings.FRACTAL_DEFAULT_GROUP_NAME}'."
151
158
  ),
152
159
  )
153
160
 
154
- # Delete
155
-
156
161
  await db.delete(group)
157
162
  await db.commit()
158
163
 
159
164
  return Response(status_code=status.HTTP_204_NO_CONTENT)
160
165
 
161
166
 
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
167
  @router_group.post("/group/{group_id}/add-user/{user_id}/", status_code=200)
188
168
  async def add_user_to_group(
189
169
  group_id: int,
190
170
  user_id: int,
191
- superuser: UserOAuth = Depends(current_active_superuser),
171
+ superuser: UserOAuth = Depends(current_superuser_act),
192
172
  db: AsyncSession = Depends(get_async_db),
193
173
  ) -> UserGroupRead:
194
174
  await _usergroup_or_404(group_id, db)
@@ -212,21 +192,24 @@ async def add_user_to_group(
212
192
  async def remove_user_from_group(
213
193
  group_id: int,
214
194
  user_id: int,
215
- superuser: UserOAuth = Depends(current_active_superuser),
195
+ superuser: UserOAuth = Depends(current_superuser_act),
216
196
  db: AsyncSession = Depends(get_async_db),
217
197
  ) -> UserGroupRead:
198
+ settings = Inject(get_settings)
218
199
  # Check that user and group exist
219
200
  await _usergroup_or_404(group_id, db)
220
201
  user = await _user_or_404(user_id, db)
221
202
 
222
203
  # Check that group is not the default one
223
- default_user_group_id = await _get_default_usergroup_id(db=db)
224
- if default_user_group_id == group_id:
204
+ default_user_group_id_or_none = await _get_default_usergroup_id_or_none(
205
+ db=db
206
+ )
207
+ if default_user_group_id_or_none == group_id:
225
208
  raise HTTPException(
226
209
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
227
210
  detail=(
228
- f"Cannot remove user from '{FRACTAL_DEFAULT_GROUP_NAME}' "
229
- "group.",
211
+ f"Cannot remove user from "
212
+ f"'{settings.FRACTAL_DEFAULT_GROUP_NAME}' group.",
230
213
  ),
231
214
  )
232
215