fractal-server 2.16.5__py3-none-any.whl → 2.17.0a0__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 (113) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +129 -22
  3. fractal_server/app/db/__init__.py +9 -11
  4. fractal_server/app/models/security.py +7 -3
  5. fractal_server/app/models/user_settings.py +0 -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 +3 -0
  10. fractal_server/app/models/v2/resource.py +130 -0
  11. fractal_server/app/models/v2/task_group.py +3 -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/profile.py +86 -0
  15. fractal_server/app/routes/admin/v2/resource.py +229 -0
  16. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +48 -82
  17. fractal_server/app/routes/api/__init__.py +26 -7
  18. fractal_server/app/routes/api/v2/_aux_functions.py +27 -1
  19. fractal_server/app/routes/api/v2/_aux_functions_history.py +2 -2
  20. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
  21. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +7 -7
  22. fractal_server/app/routes/api/v2/project.py +5 -1
  23. fractal_server/app/routes/api/v2/submit.py +32 -24
  24. fractal_server/app/routes/api/v2/task.py +5 -0
  25. fractal_server/app/routes/api/v2/task_collection.py +36 -47
  26. fractal_server/app/routes/api/v2/task_collection_custom.py +11 -5
  27. fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -40
  28. fractal_server/app/routes/api/v2/task_group_lifecycle.py +39 -82
  29. fractal_server/app/routes/api/v2/workflow_import.py +4 -3
  30. fractal_server/app/routes/auth/_aux_auth.py +3 -3
  31. fractal_server/app/routes/auth/current_user.py +45 -7
  32. fractal_server/app/routes/auth/oauth.py +1 -1
  33. fractal_server/app/routes/auth/users.py +9 -0
  34. fractal_server/app/routes/aux/_runner.py +2 -1
  35. fractal_server/app/routes/aux/validate_user_profile.py +62 -0
  36. fractal_server/app/routes/aux/validate_user_settings.py +12 -9
  37. fractal_server/app/schemas/user.py +20 -13
  38. fractal_server/app/schemas/user_settings.py +0 -4
  39. fractal_server/app/schemas/v2/__init__.py +11 -0
  40. fractal_server/app/schemas/v2/profile.py +72 -0
  41. fractal_server/app/schemas/v2/resource.py +117 -0
  42. fractal_server/app/security/__init__.py +6 -13
  43. fractal_server/app/security/signup_email.py +2 -2
  44. fractal_server/app/user_settings.py +2 -12
  45. fractal_server/config/__init__.py +23 -0
  46. fractal_server/config/_database.py +58 -0
  47. fractal_server/config/_email.py +170 -0
  48. fractal_server/config/_init_data.py +27 -0
  49. fractal_server/config/_main.py +216 -0
  50. fractal_server/config/_settings_config.py +7 -0
  51. fractal_server/images/tools.py +3 -3
  52. fractal_server/logger.py +3 -3
  53. fractal_server/main.py +14 -21
  54. fractal_server/migrations/versions/90f6508c6379_drop_useroauth_username.py +36 -0
  55. fractal_server/migrations/versions/a80ac5a352bf_resource_profile.py +195 -0
  56. fractal_server/runner/config/__init__.py +2 -0
  57. fractal_server/runner/config/_local.py +21 -0
  58. fractal_server/runner/config/_slurm.py +128 -0
  59. fractal_server/runner/config/slurm_mem_to_MB.py +63 -0
  60. fractal_server/runner/exceptions.py +4 -0
  61. fractal_server/runner/executors/base_runner.py +17 -7
  62. fractal_server/runner/executors/local/get_local_config.py +21 -86
  63. fractal_server/runner/executors/local/runner.py +48 -5
  64. fractal_server/runner/executors/slurm_common/_batching.py +2 -2
  65. fractal_server/runner/executors/slurm_common/base_slurm_runner.py +59 -25
  66. fractal_server/runner/executors/slurm_common/get_slurm_config.py +38 -54
  67. fractal_server/runner/executors/slurm_common/remote.py +1 -1
  68. fractal_server/runner/executors/slurm_common/{_slurm_config.py → slurm_config.py} +3 -254
  69. fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -1
  70. fractal_server/runner/executors/slurm_ssh/runner.py +12 -14
  71. fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -2
  72. fractal_server/runner/executors/slurm_sudo/runner.py +12 -12
  73. fractal_server/runner/v2/_local.py +36 -21
  74. fractal_server/runner/v2/_slurm_ssh.py +40 -4
  75. fractal_server/runner/v2/_slurm_sudo.py +41 -11
  76. fractal_server/runner/v2/db_tools.py +1 -1
  77. fractal_server/runner/v2/runner.py +3 -11
  78. fractal_server/runner/v2/runner_functions.py +42 -28
  79. fractal_server/runner/v2/submit_workflow.py +87 -108
  80. fractal_server/runner/versions.py +8 -3
  81. fractal_server/ssh/_fabric.py +6 -6
  82. fractal_server/tasks/config/__init__.py +3 -0
  83. fractal_server/tasks/config/_pixi.py +127 -0
  84. fractal_server/tasks/config/_python.py +51 -0
  85. fractal_server/tasks/v2/local/_utils.py +7 -7
  86. fractal_server/tasks/v2/local/collect.py +13 -5
  87. fractal_server/tasks/v2/local/collect_pixi.py +26 -10
  88. fractal_server/tasks/v2/local/deactivate.py +7 -1
  89. fractal_server/tasks/v2/local/deactivate_pixi.py +5 -1
  90. fractal_server/tasks/v2/local/delete.py +4 -0
  91. fractal_server/tasks/v2/local/reactivate.py +13 -5
  92. fractal_server/tasks/v2/local/reactivate_pixi.py +27 -9
  93. fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +11 -10
  94. fractal_server/tasks/v2/ssh/_utils.py +6 -7
  95. fractal_server/tasks/v2/ssh/collect.py +19 -12
  96. fractal_server/tasks/v2/ssh/collect_pixi.py +34 -16
  97. fractal_server/tasks/v2/ssh/deactivate.py +12 -8
  98. fractal_server/tasks/v2/ssh/deactivate_pixi.py +14 -10
  99. fractal_server/tasks/v2/ssh/delete.py +12 -9
  100. fractal_server/tasks/v2/ssh/reactivate.py +18 -12
  101. fractal_server/tasks/v2/ssh/reactivate_pixi.py +36 -17
  102. fractal_server/tasks/v2/templates/4_pip_show.sh +4 -6
  103. fractal_server/tasks/v2/utils_database.py +2 -2
  104. fractal_server/tasks/v2/utils_python_interpreter.py +8 -16
  105. fractal_server/tasks/v2/utils_templates.py +7 -10
  106. fractal_server/utils.py +1 -1
  107. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/METADATA +5 -5
  108. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/RECORD +112 -90
  109. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/WHEEL +1 -1
  110. fractal_server/config.py +0 -906
  111. /fractal_server/{runner → app}/shutdown.py +0 -0
  112. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/entry_points.txt +0 -0
  113. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,229 @@
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 func
7
+ from sqlmodel import select
8
+
9
+ from ._aux_functions import _check_resource_name
10
+ from ._aux_functions import _get_resource_or_404
11
+ from .profile import _check_profile_name
12
+ from fractal_server.app.db import AsyncSession
13
+ from fractal_server.app.db import get_async_db
14
+ from fractal_server.app.models import UserOAuth
15
+ from fractal_server.app.models.v2 import Profile
16
+ from fractal_server.app.models.v2 import Resource
17
+ from fractal_server.app.routes.auth import current_active_superuser
18
+ from fractal_server.app.schemas.v2 import ProfileCreate
19
+ from fractal_server.app.schemas.v2 import ProfileRead
20
+ from fractal_server.app.schemas.v2 import ResourceCreate
21
+ from fractal_server.app.schemas.v2 import ResourceRead
22
+ from fractal_server.config import get_settings
23
+ from fractal_server.syringe import Inject
24
+
25
+ router = APIRouter()
26
+
27
+
28
+ def _check_resource_type_match_or_422(
29
+ resource: Resource,
30
+ new_profile: ProfileCreate,
31
+ ) -> None:
32
+ if resource.type != new_profile.resource_type:
33
+ raise HTTPException(
34
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
35
+ detail=(
36
+ f"{resource.type=} differs from {new_profile.resource_type=}."
37
+ ),
38
+ )
39
+
40
+
41
+ def _check_type_match_or_422(new_resource: ResourceCreate) -> None:
42
+ """
43
+ Handle case where `resource.type != FRACTAL_RUNNER_BACKEND`
44
+ """
45
+ settings = Inject(get_settings)
46
+ if settings.FRACTAL_RUNNER_BACKEND != new_resource.type:
47
+ raise HTTPException(
48
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
49
+ detail=(
50
+ f"{settings.FRACTAL_RUNNER_BACKEND=} != "
51
+ f"{new_resource.type=}"
52
+ ),
53
+ )
54
+
55
+
56
+ @router.get("/", response_model=list[ResourceRead], status_code=200)
57
+ async def get_resource_list(
58
+ superuser: UserOAuth = Depends(current_active_superuser),
59
+ db: AsyncSession = Depends(get_async_db),
60
+ ) -> list[ResourceRead]:
61
+ """
62
+ Query `Resource` table.
63
+ """
64
+
65
+ stm = select(Resource)
66
+ res = await db.execute(stm)
67
+ resource_list = res.scalars().all()
68
+
69
+ return resource_list
70
+
71
+
72
+ @router.get("/{resource_id}/", response_model=ResourceRead, status_code=200)
73
+ async def get_resource(
74
+ resource_id: int,
75
+ superuser: UserOAuth = Depends(current_active_superuser),
76
+ db: AsyncSession = Depends(get_async_db),
77
+ ) -> ResourceRead:
78
+ """
79
+ Query single `Resource`.
80
+ """
81
+ resource = await _get_resource_or_404(resource_id=resource_id, db=db)
82
+
83
+ return resource
84
+
85
+
86
+ @router.post("/", response_model=ResourceRead, status_code=201)
87
+ async def post_resource(
88
+ resource_create: ResourceCreate,
89
+ superuser: UserOAuth = Depends(current_active_superuser),
90
+ db: AsyncSession = Depends(get_async_db),
91
+ ) -> ResourceRead:
92
+ """
93
+ Create new `Resource`.
94
+ """
95
+
96
+ # Handle case where type!=FRACTAL_RUNNER_BACKEND
97
+ _check_type_match_or_422(resource_create)
98
+
99
+ await _check_resource_name(name=resource_create.name, db=db)
100
+
101
+ resource = Resource(**resource_create.model_dump())
102
+ db.add(resource)
103
+ await db.commit()
104
+ await db.refresh(resource)
105
+
106
+ return resource
107
+
108
+
109
+ @router.put(
110
+ "/{resource_id}/",
111
+ response_model=ResourceRead,
112
+ status_code=200,
113
+ )
114
+ async def put_resource(
115
+ resource_id: int,
116
+ resource_update: ResourceCreate,
117
+ superuser: UserOAuth = Depends(current_active_superuser),
118
+ db: AsyncSession = Depends(get_async_db),
119
+ ) -> ResourceRead:
120
+ """
121
+ Overwrite a single `Resource`.
122
+ """
123
+
124
+ # Handle case where type!=FRACTAL_RUNNER_BACKEND
125
+ _check_type_match_or_422(resource_update)
126
+
127
+ resource = await _get_resource_or_404(resource_id=resource_id, db=db)
128
+
129
+ # Handle non-unique resource names
130
+ if resource_update.name and resource_update.name != resource.name:
131
+ await _check_resource_name(name=resource_update.name, db=db)
132
+
133
+ # Prepare new db object
134
+ for key, value in resource_update.model_dump().items():
135
+ setattr(resource, key, value)
136
+
137
+ await db.commit()
138
+ await db.refresh(resource)
139
+ return resource
140
+
141
+
142
+ @router.delete("/{resource_id}/", status_code=204)
143
+ async def delete_resource(
144
+ resource_id: int,
145
+ superuser: UserOAuth = Depends(current_active_superuser),
146
+ db: AsyncSession = Depends(get_async_db),
147
+ ):
148
+ """
149
+ Delete single `Resource`.
150
+ """
151
+ resource = await _get_resource_or_404(resource_id=resource_id, db=db)
152
+
153
+ # Fail if at least one Profile is associated with the Resource.
154
+ res = await db.execute(
155
+ select(func.count(Profile.id)).where(
156
+ Profile.resource_id == resource_id
157
+ )
158
+ )
159
+ associated_profile_count = res.scalar()
160
+ if associated_profile_count > 0:
161
+ raise HTTPException(
162
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
163
+ detail=(
164
+ f"Cannot delete Resource {resource_id} because it's associated"
165
+ f" with {associated_profile_count} Profiles."
166
+ ),
167
+ )
168
+
169
+ # Delete
170
+ await db.delete(resource)
171
+ await db.commit()
172
+
173
+ return Response(status_code=status.HTTP_204_NO_CONTENT)
174
+
175
+
176
+ @router.get(
177
+ "/{resource_id}/profile/",
178
+ response_model=list[ProfileRead],
179
+ status_code=200,
180
+ )
181
+ async def get_resource_profiles(
182
+ resource_id: int,
183
+ superuser: UserOAuth = Depends(current_active_superuser),
184
+ db: AsyncSession = Depends(get_async_db),
185
+ ) -> list[ProfileRead]:
186
+ """
187
+ Query `Profile`s for single `Resource`.
188
+ """
189
+ await _get_resource_or_404(resource_id=resource_id, db=db)
190
+
191
+ res = await db.execute(
192
+ select(Profile).where(Profile.resource_id == resource_id)
193
+ )
194
+ profiles = res.scalars().all()
195
+
196
+ return profiles
197
+
198
+
199
+ @router.post(
200
+ "/{resource_id}/profile/",
201
+ response_model=ProfileRead,
202
+ status_code=201,
203
+ )
204
+ async def post_profile(
205
+ resource_id: int,
206
+ profile_create: ProfileCreate,
207
+ superuser: UserOAuth = Depends(current_active_superuser),
208
+ db: AsyncSession = Depends(get_async_db),
209
+ ) -> ProfileRead:
210
+ """
211
+ Create new `Profile`.
212
+ """
213
+ resource = await _get_resource_or_404(resource_id=resource_id, db=db)
214
+
215
+ _check_resource_type_match_or_422(
216
+ resource=resource,
217
+ new_profile=profile_create,
218
+ )
219
+ await _check_profile_name(name=profile_create.name, db=db)
220
+
221
+ profile = Profile(
222
+ resource_id=resource_id,
223
+ **profile_create.model_dump(),
224
+ )
225
+
226
+ db.add(profile)
227
+ await db.commit()
228
+ await db.refresh(profile)
229
+ return profile
@@ -22,17 +22,15 @@ from fractal_server.app.routes.api.v2._aux_functions_tasks import (
22
22
  _get_task_group_or_404,
23
23
  )
24
24
  from fractal_server.app.routes.auth import current_active_superuser
25
- from fractal_server.app.routes.aux.validate_user_settings import (
26
- validate_user_settings,
25
+ from fractal_server.app.routes.aux.validate_user_profile import (
26
+ validate_user_profile,
27
27
  )
28
+ from fractal_server.app.schemas.v2 import ResourceType
28
29
  from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
29
30
  from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
30
31
  from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
31
32
  from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
32
- from fractal_server.config import get_settings
33
33
  from fractal_server.logger import set_logger
34
- from fractal_server.ssh._fabric import SSHConfig
35
- from fractal_server.syringe import Inject
36
34
  from fractal_server.tasks.v2.local import deactivate_local
37
35
  from fractal_server.tasks.v2.local import delete_local
38
36
  from fractal_server.tasks.v2.local import reactivate_local
@@ -114,34 +112,23 @@ async def deactivate_task_group(
114
112
  db.add(task_group_activity)
115
113
  await db.commit()
116
114
 
117
- # Submit background task
118
- settings = Inject(get_settings)
119
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
120
- # Validate user settings (backend-specific)
121
- user = await db.get(UserOAuth, task_group.user_id)
122
- user_settings = await validate_user_settings(
123
- user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
124
- )
125
- # User appropriate FractalSSH object
126
- ssh_config = SSHConfig(
127
- user=user_settings.ssh_username,
128
- host=user_settings.ssh_host,
129
- key_path=user_settings.ssh_private_key_path,
130
- )
115
+ user = await db.get(UserOAuth, task_group.user_id)
116
+ # Get validated resource and profile
117
+ resource, profile = await validate_user_profile(user=user, db=db)
131
118
 
132
- background_tasks.add_task(
133
- deactivate_ssh,
134
- task_group_id=task_group.id,
135
- task_group_activity_id=task_group_activity.id,
136
- ssh_config=ssh_config,
137
- tasks_base_dir=user_settings.ssh_tasks_dir,
138
- )
119
+ # Submit background task
120
+ if resource.type == ResourceType.SLURM_SSH:
121
+ deactivate_function = deactivate_ssh
139
122
  else:
140
- background_tasks.add_task(
141
- deactivate_local,
142
- task_group_id=task_group.id,
143
- task_group_activity_id=task_group_activity.id,
144
- )
123
+ deactivate_function = deactivate_local
124
+
125
+ background_tasks.add_task(
126
+ deactivate_function,
127
+ task_group_id=task_group.id,
128
+ task_group_activity_id=task_group_activity.id,
129
+ resource=resource,
130
+ profile=profile,
131
+ )
145
132
 
146
133
  logger.debug(
147
134
  "Admin task group deactivation endpoint: start deactivate "
@@ -229,34 +216,24 @@ async def reactivate_task_group(
229
216
  db.add(task_group_activity)
230
217
  await db.commit()
231
218
 
232
- # Submit background task
233
- settings = Inject(get_settings)
234
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
235
- # Validate user settings (backend-specific)
236
- user = await db.get(UserOAuth, task_group.user_id)
237
- user_settings = await validate_user_settings(
238
- user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
239
- )
240
- # Use appropriate FractalSSH object
241
- ssh_config = SSHConfig(
242
- user=user_settings.ssh_username,
243
- host=user_settings.ssh_host,
244
- key_path=user_settings.ssh_private_key_path,
245
- )
219
+ # Get validated resource and profile
220
+ user = await db.get(UserOAuth, task_group.user_id)
221
+ resource, profile = await validate_user_profile(user=user, db=db)
246
222
 
247
- background_tasks.add_task(
248
- reactivate_ssh,
249
- task_group_id=task_group.id,
250
- task_group_activity_id=task_group_activity.id,
251
- ssh_config=ssh_config,
252
- tasks_base_dir=user_settings.ssh_tasks_dir,
253
- )
223
+ # Submit background task
224
+ if resource.type == ResourceType.SLURM_SSH:
225
+ reactivate_function = reactivate_ssh
254
226
  else:
255
- background_tasks.add_task(
256
- reactivate_local,
257
- task_group_id=task_group.id,
258
- task_group_activity_id=task_group_activity.id,
259
- )
227
+ reactivate_function = reactivate_local
228
+
229
+ background_tasks.add_task(
230
+ reactivate_function,
231
+ task_group_id=task_group.id,
232
+ task_group_activity_id=task_group_activity.id,
233
+ resource=resource,
234
+ profile=profile,
235
+ )
236
+
260
237
  logger.debug(
261
238
  "Admin task group reactivation endpoint: start reactivate "
262
239
  "and return task_group_activity"
@@ -292,33 +269,22 @@ async def delete_task_group(
292
269
  db.add(task_group_activity)
293
270
  await db.commit()
294
271
 
295
- settings = Inject(get_settings)
296
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
297
- # Validate user settings (backend-specific)
298
- task_owner = await db.get(UserOAuth, task_group.user_id)
299
- task_owner_settings = await validate_user_settings(
300
- user=task_owner, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
301
- )
302
- # Use appropriate FractalSSH object
303
- ssh_config = SSHConfig(
304
- user=task_owner_settings.ssh_username,
305
- host=task_owner_settings.ssh_host,
306
- key_path=task_owner_settings.ssh_private_key_path,
307
- )
272
+ # Get validated resource and profile
273
+ task_owner = await db.get(UserOAuth, task_group.user_id)
274
+ resource, profile = await validate_user_profile(user=task_owner, db=db)
308
275
 
309
- background_tasks.add_task(
310
- delete_ssh,
311
- task_group_id=task_group.id,
312
- task_group_activity_id=task_group_activity.id,
313
- ssh_config=ssh_config,
314
- tasks_base_dir=task_owner_settings.ssh_tasks_dir,
315
- )
276
+ if resource.type == ResourceType.SLURM_SSH:
277
+ delete_function = delete_ssh
316
278
  else:
317
- background_tasks.add_task(
318
- delete_local,
319
- task_group_id=task_group.id,
320
- task_group_activity_id=task_group_activity.id,
321
- )
279
+ delete_function = delete_local
280
+
281
+ background_tasks.add_task(
282
+ delete_function,
283
+ task_group_activity_id=task_group_activity.id,
284
+ task_group_id=task_group.id,
285
+ resource=resource,
286
+ profile=profile,
287
+ )
322
288
  logger.debug(
323
289
  "Admin task group deletion endpoint: start deletion "
324
290
  "and return task_group_activity"
@@ -4,25 +4,44 @@
4
4
  from fastapi import APIRouter
5
5
  from fastapi import Depends
6
6
 
7
- from ....config import get_settings
8
- from ....syringe import Inject
7
+ import fractal_server
9
8
  from fractal_server.app.models import UserOAuth
10
9
  from fractal_server.app.routes.auth import current_active_superuser
11
-
10
+ from fractal_server.config import get_db_settings
11
+ from fractal_server.config import get_email_settings
12
+ from fractal_server.config import get_settings
13
+ from fractal_server.syringe import Inject
12
14
 
13
15
  router_api = APIRouter()
14
16
 
15
17
 
16
18
  @router_api.get("/alive/")
17
19
  async def alive():
18
- settings = Inject(get_settings)
19
20
  return dict(
20
21
  alive=True,
21
- version=settings.PROJECT_VERSION,
22
+ version=fractal_server.__VERSION__,
22
23
  )
23
24
 
24
25
 
25
- @router_api.get("/settings/")
26
- async def view_settings(user: UserOAuth = Depends(current_active_superuser)):
26
+ @router_api.get("/settings/app/")
27
+ async def view_settings(
28
+ user: UserOAuth = Depends(current_active_superuser),
29
+ ):
27
30
  settings = Inject(get_settings)
28
31
  return settings.model_dump()
32
+
33
+
34
+ @router_api.get("/settings/database/")
35
+ async def view_db_settings(
36
+ user: UserOAuth = Depends(current_active_superuser),
37
+ ):
38
+ settings = Inject(get_db_settings)
39
+ return settings.model_dump()
40
+
41
+
42
+ @router_api.get("/settings/email/")
43
+ async def view_email_settings(
44
+ user: UserOAuth = Depends(current_active_superuser),
45
+ ):
46
+ settings = Inject(get_email_settings)
47
+ return settings.model_dump()
@@ -11,7 +11,6 @@ from sqlalchemy.orm.attributes import flag_modified
11
11
  from sqlmodel import select
12
12
  from sqlmodel.sql.expression import SelectOfScalar
13
13
 
14
- from ....db import AsyncSession
15
14
  from ....models.v2 import DatasetV2
16
15
  from ....models.v2 import JobV2
17
16
  from ....models.v2 import LinkUserProjectV2
@@ -20,6 +19,10 @@ from ....models.v2 import TaskV2
20
19
  from ....models.v2 import WorkflowTaskV2
21
20
  from ....models.v2 import WorkflowV2
22
21
  from ....schemas.v2 import JobStatusTypeV2
22
+ from fractal_server.app.db import AsyncSession
23
+ from fractal_server.app.models import Profile
24
+ from fractal_server.app.models import Resource
25
+ from fractal_server.app.models import UserOAuth
23
26
  from fractal_server.logger import set_logger
24
27
 
25
28
  logger = set_logger(__name__)
@@ -538,3 +541,26 @@ async def _get_submitted_job_or_none(
538
541
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
539
542
  detail=error_msg,
540
543
  )
544
+
545
+
546
+ async def _get_resource_and_profile_ids(
547
+ *,
548
+ user_id: int,
549
+ db: AsyncSession,
550
+ ) -> tuple[int, int] | tuple[None, None]:
551
+ """
552
+ Get `(resource_id, profile_id)` pair for a given user, or `(None,None)`.
553
+ """
554
+ stm = (
555
+ select(Resource.id, Profile.id)
556
+ .join(UserOAuth)
557
+ .where(Resource.id == Profile.resource_id)
558
+ .where(Profile.id == UserOAuth.profile_id)
559
+ .where(UserOAuth.id == user_id)
560
+ )
561
+ res = await db.execute(stm)
562
+ data = res.one_or_none()
563
+ if data is None:
564
+ return (None, None)
565
+ else:
566
+ return tuple(data)
@@ -32,7 +32,7 @@ async def get_history_unit_or_404(
32
32
  """
33
33
  Get an existing HistoryUnit or raise a 404.
34
34
 
35
- Arguments:
35
+ Args:
36
36
  history_unit_id: The `HistoryUnit` id
37
37
  db: An asynchronous db session
38
38
  """
@@ -51,7 +51,7 @@ async def get_history_run_or_404(
51
51
  """
52
52
  Get an existing HistoryRun or raise a 404.
53
53
 
54
- Arguments:
54
+ Args:
55
55
  history_run_id:
56
56
  db:
57
57
  """
@@ -59,7 +59,7 @@ async def get_package_version_from_pypi(
59
59
 
60
60
  Ref https://warehouse.pypa.io/api-reference/json.html.
61
61
 
62
- Arguments:
62
+ Args:
63
63
  name: Package name.
64
64
  version:
65
65
  Could be a correct version (`1.3.0`), an incomplete one
@@ -165,7 +165,7 @@ async def check_no_ongoing_activity(
165
165
  """
166
166
  Find ongoing activities for the same task group.
167
167
 
168
- Arguments:
168
+ Args:
169
169
  task_group_id:
170
170
  db:
171
171
  """
@@ -204,7 +204,7 @@ async def check_no_submitted_job(
204
204
  """
205
205
  Find submitted jobs which include tasks from a given task group.
206
206
 
207
- Arguments:
207
+ Args:
208
208
  task_group_id: ID of the `TaskGroupV2` object.
209
209
  db: Asynchronous database session.
210
210
  """
@@ -33,7 +33,7 @@ async def _get_task_group_or_404(
33
33
  """
34
34
  Get an existing task group or raise a 404.
35
35
 
36
- Arguments:
36
+ Args:
37
37
  task_group_id: The TaskGroupV2 id
38
38
  db: An asynchronous db session
39
39
  """
@@ -55,7 +55,7 @@ async def _get_task_group_read_access(
55
55
  """
56
56
  Get a task group or raise a 403 if user has no read access.
57
57
 
58
- Arguments:
58
+ Args:
59
59
  task_group_id: ID of the required task group.
60
60
  user_id: ID of the current user.
61
61
  db: An asynchronous db session.
@@ -100,7 +100,7 @@ async def _get_task_group_full_access(
100
100
  """
101
101
  Get a task group or raise a 403 if user has no full access.
102
102
 
103
- Arguments:
103
+ Args:
104
104
  task_group_id: ID of the required task group.
105
105
  user_id: ID of the current user.
106
106
  db: An asynchronous db session
@@ -125,7 +125,7 @@ async def _get_task_or_404(*, task_id: int, db: AsyncSession) -> TaskV2:
125
125
  """
126
126
  Get an existing task or raise a 404.
127
127
 
128
- Arguments:
128
+ Args:
129
129
  task_id: ID of the required task.
130
130
  db: An asynchronous db session
131
131
  """
@@ -147,7 +147,7 @@ async def _get_task_full_access(
147
147
  """
148
148
  Get an existing task or raise a 404.
149
149
 
150
- Arguments:
150
+ Args:
151
151
  task_id: ID of the required task.
152
152
  user_id: ID of the current user.
153
153
  db: An asynchronous db session.
@@ -169,7 +169,7 @@ async def _get_task_read_access(
169
169
  """
170
170
  Get an existing task or raise a 404.
171
171
 
172
- Arguments:
172
+ Args:
173
173
  task_id: ID of the required task.
174
174
  user_id: ID of the current user.
175
175
  db: An asynchronous db session.
@@ -198,7 +198,7 @@ async def _get_valid_user_group_id(
198
198
  """
199
199
  Validate query parameters for endpoints that create some task(s).
200
200
 
201
- Arguments:
201
+ Args:
202
202
  user_group_id:
203
203
  private:
204
204
  user_id: ID of the current user
@@ -17,6 +17,7 @@ from ....schemas.v2 import ProjectReadV2
17
17
  from ....schemas.v2 import ProjectUpdateV2
18
18
  from ._aux_functions import _check_project_exists
19
19
  from ._aux_functions import _get_project_check_owner
20
+ from ._aux_functions import _get_resource_and_profile_ids
20
21
  from ._aux_functions import _get_submitted_jobs_statement
21
22
  from fractal_server.app.models import UserOAuth
22
23
  from fractal_server.app.routes.auth import current_active_user
@@ -57,8 +58,11 @@ async def create_project(
57
58
  await _check_project_exists(
58
59
  project_name=project.name, user_id=user.id, db=db
59
60
  )
61
+ resource_id, _ = await _get_resource_and_profile_ids(
62
+ user_id=user.id, db=db
63
+ )
60
64
 
61
- db_project = ProjectV2(**project.model_dump())
65
+ db_project = ProjectV2(**project.model_dump(), resource_id=resource_id)
62
66
  db_project.user_list.append(user)
63
67
 
64
68
  db.add(db_project)