fractal-server 2.16.6__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 (142) 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/profile.py +16 -0
  8. fractal_server/app/models/v2/project.py +5 -0
  9. fractal_server/app/models/v2/resource.py +130 -0
  10. fractal_server/app/models/v2/task_group.py +4 -0
  11. fractal_server/app/routes/admin/v2/__init__.py +4 -0
  12. fractal_server/app/routes/admin/v2/_aux_functions.py +55 -0
  13. fractal_server/app/routes/admin/v2/accounting.py +3 -3
  14. fractal_server/app/routes/admin/v2/impersonate.py +2 -2
  15. fractal_server/app/routes/admin/v2/job.py +51 -15
  16. fractal_server/app/routes/admin/v2/profile.py +100 -0
  17. fractal_server/app/routes/admin/v2/project.py +2 -2
  18. fractal_server/app/routes/admin/v2/resource.py +222 -0
  19. fractal_server/app/routes/admin/v2/task.py +59 -32
  20. fractal_server/app/routes/admin/v2/task_group.py +17 -12
  21. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +52 -86
  22. fractal_server/app/routes/api/__init__.py +45 -8
  23. fractal_server/app/routes/api/v2/_aux_functions.py +17 -1
  24. fractal_server/app/routes/api/v2/_aux_functions_history.py +2 -2
  25. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
  26. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +55 -19
  27. fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +21 -17
  28. fractal_server/app/routes/api/v2/dataset.py +10 -19
  29. fractal_server/app/routes/api/v2/history.py +8 -8
  30. fractal_server/app/routes/api/v2/images.py +5 -5
  31. fractal_server/app/routes/api/v2/job.py +8 -8
  32. fractal_server/app/routes/api/v2/pre_submission_checks.py +3 -3
  33. fractal_server/app/routes/api/v2/project.py +15 -7
  34. fractal_server/app/routes/api/v2/status_legacy.py +2 -2
  35. fractal_server/app/routes/api/v2/submit.py +49 -42
  36. fractal_server/app/routes/api/v2/task.py +26 -8
  37. fractal_server/app/routes/api/v2/task_collection.py +39 -50
  38. fractal_server/app/routes/api/v2/task_collection_custom.py +10 -6
  39. fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -42
  40. fractal_server/app/routes/api/v2/task_group.py +19 -9
  41. fractal_server/app/routes/api/v2/task_group_lifecycle.py +43 -86
  42. fractal_server/app/routes/api/v2/task_version_update.py +3 -3
  43. fractal_server/app/routes/api/v2/workflow.py +9 -9
  44. fractal_server/app/routes/api/v2/workflow_import.py +25 -13
  45. fractal_server/app/routes/api/v2/workflowtask.py +5 -5
  46. fractal_server/app/routes/auth/__init__.py +34 -5
  47. fractal_server/app/routes/auth/_aux_auth.py +39 -20
  48. fractal_server/app/routes/auth/current_user.py +56 -67
  49. fractal_server/app/routes/auth/group.py +29 -46
  50. fractal_server/app/routes/auth/oauth.py +55 -38
  51. fractal_server/app/routes/auth/register.py +2 -2
  52. fractal_server/app/routes/auth/router.py +4 -2
  53. fractal_server/app/routes/auth/users.py +29 -53
  54. fractal_server/app/routes/aux/_runner.py +2 -1
  55. fractal_server/app/routes/aux/validate_user_profile.py +62 -0
  56. fractal_server/app/schemas/__init__.py +0 -1
  57. fractal_server/app/schemas/user.py +43 -13
  58. fractal_server/app/schemas/user_group.py +2 -1
  59. fractal_server/app/schemas/v2/__init__.py +12 -0
  60. fractal_server/app/schemas/v2/profile.py +78 -0
  61. fractal_server/app/schemas/v2/resource.py +137 -0
  62. fractal_server/app/schemas/v2/task_collection.py +11 -3
  63. fractal_server/app/schemas/v2/task_group.py +5 -0
  64. fractal_server/app/security/__init__.py +174 -75
  65. fractal_server/app/security/signup_email.py +52 -34
  66. fractal_server/config/__init__.py +27 -0
  67. fractal_server/config/_data.py +68 -0
  68. fractal_server/config/_database.py +59 -0
  69. fractal_server/config/_email.py +133 -0
  70. fractal_server/config/_main.py +78 -0
  71. fractal_server/config/_oauth.py +69 -0
  72. fractal_server/config/_settings_config.py +7 -0
  73. fractal_server/data_migrations/2_17_0.py +339 -0
  74. fractal_server/images/tools.py +3 -3
  75. fractal_server/logger.py +3 -3
  76. fractal_server/main.py +17 -23
  77. fractal_server/migrations/naming_convention.py +1 -1
  78. fractal_server/migrations/versions/83bc2ad3ffcc_2_17_0.py +195 -0
  79. fractal_server/runner/config/__init__.py +2 -0
  80. fractal_server/runner/config/_local.py +21 -0
  81. fractal_server/runner/config/_slurm.py +129 -0
  82. fractal_server/runner/config/slurm_mem_to_MB.py +63 -0
  83. fractal_server/runner/exceptions.py +4 -0
  84. fractal_server/runner/executors/base_runner.py +17 -7
  85. fractal_server/runner/executors/local/get_local_config.py +21 -86
  86. fractal_server/runner/executors/local/runner.py +48 -5
  87. fractal_server/runner/executors/slurm_common/_batching.py +2 -2
  88. fractal_server/runner/executors/slurm_common/base_slurm_runner.py +60 -26
  89. fractal_server/runner/executors/slurm_common/get_slurm_config.py +39 -55
  90. fractal_server/runner/executors/slurm_common/remote.py +1 -1
  91. fractal_server/runner/executors/slurm_common/slurm_config.py +214 -0
  92. fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -1
  93. fractal_server/runner/executors/slurm_ssh/runner.py +12 -14
  94. fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -2
  95. fractal_server/runner/executors/slurm_sudo/runner.py +12 -12
  96. fractal_server/runner/v2/_local.py +36 -21
  97. fractal_server/runner/v2/_slurm_ssh.py +41 -4
  98. fractal_server/runner/v2/_slurm_sudo.py +42 -12
  99. fractal_server/runner/v2/db_tools.py +1 -1
  100. fractal_server/runner/v2/runner.py +3 -11
  101. fractal_server/runner/v2/runner_functions.py +42 -28
  102. fractal_server/runner/v2/submit_workflow.py +88 -109
  103. fractal_server/runner/versions.py +8 -3
  104. fractal_server/ssh/_fabric.py +6 -6
  105. fractal_server/tasks/config/__init__.py +3 -0
  106. fractal_server/tasks/config/_pixi.py +127 -0
  107. fractal_server/tasks/config/_python.py +51 -0
  108. fractal_server/tasks/v2/local/_utils.py +7 -7
  109. fractal_server/tasks/v2/local/collect.py +13 -5
  110. fractal_server/tasks/v2/local/collect_pixi.py +26 -10
  111. fractal_server/tasks/v2/local/deactivate.py +7 -1
  112. fractal_server/tasks/v2/local/deactivate_pixi.py +5 -1
  113. fractal_server/tasks/v2/local/delete.py +5 -1
  114. fractal_server/tasks/v2/local/reactivate.py +13 -5
  115. fractal_server/tasks/v2/local/reactivate_pixi.py +27 -9
  116. fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +11 -10
  117. fractal_server/tasks/v2/ssh/_utils.py +6 -7
  118. fractal_server/tasks/v2/ssh/collect.py +19 -12
  119. fractal_server/tasks/v2/ssh/collect_pixi.py +34 -16
  120. fractal_server/tasks/v2/ssh/deactivate.py +12 -8
  121. fractal_server/tasks/v2/ssh/deactivate_pixi.py +14 -10
  122. fractal_server/tasks/v2/ssh/delete.py +12 -9
  123. fractal_server/tasks/v2/ssh/reactivate.py +18 -12
  124. fractal_server/tasks/v2/ssh/reactivate_pixi.py +36 -17
  125. fractal_server/tasks/v2/templates/4_pip_show.sh +4 -6
  126. fractal_server/tasks/v2/utils_database.py +2 -2
  127. fractal_server/tasks/v2/utils_pixi.py +3 -0
  128. fractal_server/tasks/v2/utils_python_interpreter.py +8 -16
  129. fractal_server/tasks/v2/utils_templates.py +7 -10
  130. fractal_server/utils.py +1 -1
  131. {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/METADATA +4 -6
  132. {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/RECORD +136 -117
  133. fractal_server/app/routes/aux/validate_user_settings.py +0 -73
  134. fractal_server/app/schemas/user_settings.py +0 -67
  135. fractal_server/app/user_settings.py +0 -42
  136. fractal_server/config.py +0 -906
  137. fractal_server/data_migrations/2_14_10.py +0 -48
  138. fractal_server/runner/executors/slurm_common/_slurm_config.py +0 -471
  139. /fractal_server/{runner → app}/shutdown.py +0 -0
  140. {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/WHEEL +0 -0
  141. {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/entry_points.txt +0 -0
  142. {fractal_server-2.16.6.dist-info → fractal_server-2.17.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,5 @@
1
1
  from fastapi import APIRouter
2
2
  from fastapi import Depends
3
- from fastapi import HTTPException
4
- from fastapi import status
5
3
  from pydantic import BaseModel
6
4
  from pydantic import EmailStr
7
5
  from pydantic import Field
@@ -10,11 +8,16 @@ from sqlmodel import select
10
8
 
11
9
  from fractal_server.app.db import AsyncSession
12
10
  from fractal_server.app.db import get_async_db
11
+ from fractal_server.app.models import TaskGroupV2
13
12
  from fractal_server.app.models import UserOAuth
14
13
  from fractal_server.app.models.v2 import TaskV2
15
14
  from fractal_server.app.models.v2 import WorkflowTaskV2
16
15
  from fractal_server.app.models.v2 import WorkflowV2
17
- from fractal_server.app.routes.auth import current_active_superuser
16
+ from fractal_server.app.routes.auth import current_superuser_act
17
+ from fractal_server.app.routes.pagination import get_pagination_params
18
+ from fractal_server.app.routes.pagination import PaginationRequest
19
+ from fractal_server.app.routes.pagination import PaginationResponse
20
+ from fractal_server.app.schemas.v2.task import TaskType
18
21
 
19
22
  router = APIRouter()
20
23
 
@@ -48,66 +51,85 @@ class TaskV2Info(BaseModel):
48
51
  relationships: list[TaskV2Relationship]
49
52
 
50
53
 
51
- @router.get("/", response_model=list[TaskV2Info])
54
+ @router.get("/", response_model=PaginationResponse[TaskV2Info])
52
55
  async def query_tasks(
53
56
  id: int | None = None,
54
57
  source: str | None = None,
55
58
  version: str | None = None,
56
59
  name: str | None = None,
57
- max_number_of_results: int = 25,
60
+ task_type: TaskType | None = None,
58
61
  category: str | None = None,
59
62
  modality: str | None = None,
60
63
  author: str | None = None,
61
- user: UserOAuth = Depends(current_active_superuser),
64
+ resource_id: int | None = None,
65
+ pagination: PaginationRequest = Depends(get_pagination_params),
66
+ user: UserOAuth = Depends(current_superuser_act),
62
67
  db: AsyncSession = Depends(get_async_db),
63
- ) -> list[TaskV2Info]:
68
+ ) -> PaginationResponse[TaskV2Info]:
64
69
  """
65
- Query `TaskV2` table and get information about related items
66
- (WorkflowV2s and ProjectV2s)
67
-
68
- Args:
69
- id: If not `None`, query for matching `task.id`.
70
- source: If not `None`, query for contained case insensitive
71
- `task.source`.
72
- version: If not `None`, query for matching `task.version`.
73
- name: If not `None`, query for contained case insensitive `task.name`.
74
- max_number_of_results: The maximum length of the response.
75
- category:
76
- modality:
77
- author:
70
+ Query `TaskV2` and get information about related workflows and projects.
78
71
  """
79
72
 
80
- stm = select(TaskV2)
73
+ # Assign pagination parameters
74
+ page = pagination.page
75
+ page_size = pagination.page_size
81
76
 
77
+ # Prepare statements
78
+ stm = select(TaskV2).order_by(TaskV2.id)
79
+ stm_count = select(func.count(TaskV2.id))
82
80
  if id is not None:
83
81
  stm = stm.where(TaskV2.id == id)
82
+ stm_count = stm_count.where(TaskV2.id == id)
84
83
  if source is not None:
85
84
  stm = stm.where(TaskV2.source.icontains(source))
85
+ stm_count = stm_count.where(TaskV2.source.icontains(source))
86
86
  if version is not None:
87
87
  stm = stm.where(TaskV2.version == version)
88
+ stm_count = stm_count.where(TaskV2.version == version)
88
89
  if name is not None:
89
90
  stm = stm.where(TaskV2.name.icontains(name))
91
+ stm_count = stm_count.where(TaskV2.name.icontains(name))
92
+ if task_type is not None:
93
+ stm = stm.where(TaskV2.type == task_type)
94
+ stm_count = stm_count.where(TaskV2.type == task_type)
90
95
  if category is not None:
91
96
  stm = stm.where(func.lower(TaskV2.category) == category.lower())
97
+ stm_count = stm_count.where(
98
+ func.lower(TaskV2.category) == category.lower()
99
+ )
92
100
  if modality is not None:
93
101
  stm = stm.where(func.lower(TaskV2.modality) == modality.lower())
102
+ stm_count = stm_count.where(
103
+ func.lower(TaskV2.modality) == modality.lower()
104
+ )
94
105
  if author is not None:
95
106
  stm = stm.where(TaskV2.authors.icontains(author))
107
+ stm_count = stm_count.where(TaskV2.authors.icontains(author))
108
+ if resource_id is not None:
109
+ stm = (
110
+ stm.join(TaskGroupV2)
111
+ .where(TaskGroupV2.id == TaskV2.taskgroupv2_id)
112
+ .where(TaskGroupV2.resource_id == resource_id)
113
+ )
114
+ stm_count = (
115
+ stm_count.join(TaskGroupV2)
116
+ .where(TaskGroupV2.id == TaskV2.taskgroupv2_id)
117
+ .where(TaskGroupV2.resource_id == resource_id)
118
+ )
119
+
120
+ # Find total number of elements
121
+ res_total_count = await db.execute(stm_count)
122
+ total_count = res_total_count.scalar()
123
+ if page_size is None:
124
+ page_size = total_count
125
+ else:
126
+ stm = stm.offset((page - 1) * page_size).limit(page_size)
96
127
 
128
+ # Get `page_size` rows
97
129
  res = await db.execute(stm)
98
130
  task_list = res.scalars().all()
99
- if len(task_list) > max_number_of_results:
100
- await db.close()
101
- raise HTTPException(
102
- status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
103
- detail=(
104
- f"Too many Tasks ({len(task_list)} > {max_number_of_results})."
105
- " Please add more query filters."
106
- ),
107
- )
108
131
 
109
132
  task_info_list = []
110
-
111
133
  for task in task_list:
112
134
  stm = (
113
135
  select(WorkflowV2)
@@ -137,4 +159,9 @@ async def query_tasks(
137
159
  )
138
160
  )
139
161
 
140
- return task_info_list
162
+ return PaginationResponse[TaskV2Info](
163
+ total_count=total_count,
164
+ page_size=page_size,
165
+ current_page=page,
166
+ items=task_info_list,
167
+ )
@@ -12,18 +12,19 @@ from fractal_server.app.db import get_async_db
12
12
  from fractal_server.app.models import UserOAuth
13
13
  from fractal_server.app.models.v2 import TaskGroupActivityV2
14
14
  from fractal_server.app.models.v2 import TaskGroupV2
15
- from fractal_server.app.routes.auth import current_active_superuser
15
+ from fractal_server.app.routes.auth import current_superuser_act
16
16
  from fractal_server.app.routes.auth._aux_auth import (
17
17
  _verify_user_belongs_to_group,
18
18
  )
19
19
  from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
20
20
  from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
21
21
  from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
22
- from fractal_server.app.schemas.v2 import TaskGroupReadV2
22
+ from fractal_server.app.schemas.v2 import TaskGroupReadSuperuser
23
23
  from fractal_server.app.schemas.v2 import TaskGroupUpdateV2
24
24
  from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
25
25
  from fractal_server.logger import set_logger
26
26
 
27
+
27
28
  router = APIRouter()
28
29
 
29
30
  logger = set_logger(__name__)
@@ -38,7 +39,7 @@ async def get_task_group_activity_list(
38
39
  status: TaskGroupActivityStatusV2 | None = None,
39
40
  action: TaskGroupActivityActionV2 | None = None,
40
41
  timestamp_started_min: AwareDatetime | None = None,
41
- superuser: UserOAuth = Depends(current_active_superuser),
42
+ superuser: UserOAuth = Depends(current_superuser_act),
42
43
  db: AsyncSession = Depends(get_async_db),
43
44
  ) -> list[TaskGroupActivityV2Read]:
44
45
  stm = select(TaskGroupActivityV2)
@@ -64,12 +65,12 @@ async def get_task_group_activity_list(
64
65
  return activities
65
66
 
66
67
 
67
- @router.get("/{task_group_id}/", response_model=TaskGroupReadV2)
68
+ @router.get("/{task_group_id}/", response_model=TaskGroupReadSuperuser)
68
69
  async def query_task_group(
69
70
  task_group_id: int,
70
- user: UserOAuth = Depends(current_active_superuser),
71
+ user: UserOAuth = Depends(current_superuser_act),
71
72
  db: AsyncSession = Depends(get_async_db),
72
- ) -> TaskGroupReadV2:
73
+ ) -> TaskGroupReadSuperuser:
73
74
  task_group = await db.get(TaskGroupV2, task_group_id)
74
75
  if task_group is None:
75
76
  raise HTTPException(
@@ -79,7 +80,7 @@ async def query_task_group(
79
80
  return task_group
80
81
 
81
82
 
82
- @router.get("/", response_model=list[TaskGroupReadV2])
83
+ @router.get("/", response_model=list[TaskGroupReadSuperuser])
83
84
  async def query_task_group_list(
84
85
  user_id: int | None = None,
85
86
  user_group_id: int | None = None,
@@ -89,9 +90,10 @@ async def query_task_group_list(
89
90
  origin: TaskGroupV2OriginEnum | None = None,
90
91
  timestamp_last_used_min: AwareDatetime | None = None,
91
92
  timestamp_last_used_max: AwareDatetime | None = None,
92
- user: UserOAuth = Depends(current_active_superuser),
93
+ resource_id: int | None = None,
94
+ user: UserOAuth = Depends(current_superuser_act),
93
95
  db: AsyncSession = Depends(get_async_db),
94
- ) -> list[TaskGroupReadV2]:
96
+ ) -> list[TaskGroupReadSuperuser]:
95
97
  stm = select(TaskGroupV2)
96
98
 
97
99
  if user_group_id is not None and private is True:
@@ -128,19 +130,22 @@ async def query_task_group_list(
128
130
  stm = stm.where(
129
131
  TaskGroupV2.timestamp_last_used <= timestamp_last_used_max
130
132
  )
133
+ if resource_id is not None:
134
+ stm = stm.where(TaskGroupV2.resource_id == resource_id)
131
135
 
136
+ stm = stm.order_by(TaskGroupV2.id)
132
137
  res = await db.execute(stm)
133
138
  task_groups_list = res.scalars().all()
134
139
  return task_groups_list
135
140
 
136
141
 
137
- @router.patch("/{task_group_id}/", response_model=TaskGroupReadV2)
142
+ @router.patch("/{task_group_id}/", response_model=TaskGroupReadSuperuser)
138
143
  async def patch_task_group(
139
144
  task_group_id: int,
140
145
  task_group_update: TaskGroupUpdateV2,
141
- user: UserOAuth = Depends(current_active_superuser),
146
+ user: UserOAuth = Depends(current_superuser_act),
142
147
  db: AsyncSession = Depends(get_async_db),
143
- ) -> list[TaskGroupReadV2]:
148
+ ) -> list[TaskGroupReadSuperuser]:
144
149
  task_group = await db.get(TaskGroupV2, task_group_id)
145
150
  if task_group is None:
146
151
  raise HTTPException(
@@ -21,18 +21,16 @@ from fractal_server.app.routes.api.v2._aux_functions_task_lifecycle import (
21
21
  from fractal_server.app.routes.api.v2._aux_functions_tasks import (
22
22
  _get_task_group_or_404,
23
23
  )
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,
24
+ from fractal_server.app.routes.auth import current_superuser_act
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
@@ -54,7 +52,7 @@ async def deactivate_task_group(
54
52
  task_group_id: int,
55
53
  background_tasks: BackgroundTasks,
56
54
  response: Response,
57
- superuser: UserOAuth = Depends(current_active_superuser),
55
+ superuser: UserOAuth = Depends(current_superuser_act),
58
56
  db: AsyncSession = Depends(get_async_db),
59
57
  ) -> TaskGroupActivityV2Read:
60
58
  """
@@ -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 "
@@ -159,7 +146,7 @@ async def reactivate_task_group(
159
146
  task_group_id: int,
160
147
  background_tasks: BackgroundTasks,
161
148
  response: Response,
162
- superuser: UserOAuth = Depends(current_active_superuser),
149
+ superuser: UserOAuth = Depends(current_superuser_act),
163
150
  db: AsyncSession = Depends(get_async_db),
164
151
  ) -> TaskGroupActivityV2Read:
165
152
  """
@@ -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"
@@ -270,7 +247,7 @@ async def delete_task_group(
270
247
  task_group_id: int,
271
248
  background_tasks: BackgroundTasks,
272
249
  response: Response,
273
- superuser: UserOAuth = Depends(current_active_superuser),
250
+ superuser: UserOAuth = Depends(current_superuser_act),
274
251
  db: AsyncSession = Depends(get_async_db),
275
252
  ):
276
253
  task_group = await _get_task_group_or_404(
@@ -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,62 @@
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
- from fractal_server.app.routes.auth import current_active_superuser
11
-
9
+ from fractal_server.app.routes.auth import current_superuser_act
10
+ from fractal_server.config import get_data_settings
11
+ from fractal_server.config import get_db_settings
12
+ from fractal_server.config import get_email_settings
13
+ from fractal_server.config import get_oauth_settings
14
+ from fractal_server.config import get_settings
15
+ from fractal_server.syringe import Inject
12
16
 
13
17
  router_api = APIRouter()
14
18
 
15
19
 
16
20
  @router_api.get("/alive/")
17
21
  async def alive():
18
- settings = Inject(get_settings)
19
22
  return dict(
20
23
  alive=True,
21
- version=settings.PROJECT_VERSION,
24
+ version=fractal_server.__VERSION__,
22
25
  )
23
26
 
24
27
 
25
- @router_api.get("/settings/")
26
- async def view_settings(user: UserOAuth = Depends(current_active_superuser)):
28
+ @router_api.get("/settings/app/")
29
+ async def view_settings(
30
+ user: UserOAuth = Depends(current_superuser_act),
31
+ ):
27
32
  settings = Inject(get_settings)
28
33
  return settings.model_dump()
34
+
35
+
36
+ @router_api.get("/settings/database/")
37
+ async def view_db_settings(
38
+ user: UserOAuth = Depends(current_superuser_act),
39
+ ):
40
+ settings = Inject(get_db_settings)
41
+ return settings.model_dump()
42
+
43
+
44
+ @router_api.get("/settings/email/")
45
+ async def view_email_settings(
46
+ user: UserOAuth = Depends(current_superuser_act),
47
+ ):
48
+ settings = Inject(get_email_settings)
49
+ return settings.model_dump()
50
+
51
+
52
+ @router_api.get("/settings/data/")
53
+ async def view_data_settings(
54
+ user: UserOAuth = Depends(current_superuser_act),
55
+ ):
56
+ settings = Inject(get_data_settings)
57
+ return settings.model_dump()
58
+
59
+
60
+ @router_api.get("/settings/oauth/")
61
+ async def view_oauth_settings(
62
+ user: UserOAuth = Depends(current_superuser_act),
63
+ ):
64
+ settings = Inject(get_oauth_settings)
65
+ 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,16 @@ 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_user_resource_id(user_id: int, db: AsyncSession) -> int | None:
547
+ res = await db.execute(
548
+ select(Resource.id)
549
+ .join(Profile)
550
+ .join(UserOAuth)
551
+ .where(Resource.id == Profile.resource_id)
552
+ .where(Profile.id == UserOAuth.profile_id)
553
+ .where(UserOAuth.id == user_id)
554
+ )
555
+ resource_id = res.scalar_one_or_none()
556
+ return resource_id
@@ -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
  """