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
@@ -10,6 +10,7 @@ from fastapi import Request
10
10
  from fastapi import status
11
11
  from sqlmodel import select
12
12
 
13
+ from ...aux.validate_user_profile import validate_user_profile
13
14
  from ._aux_functions import _get_dataset_check_owner
14
15
  from ._aux_functions import _get_workflow_check_owner
15
16
  from ._aux_functions import clean_app_job_list_v2
@@ -29,6 +30,7 @@ from fractal_server.app.routes.aux.validate_user_settings import (
29
30
  from fractal_server.app.schemas.v2 import JobCreateV2
30
31
  from fractal_server.app.schemas.v2 import JobReadV2
31
32
  from fractal_server.app.schemas.v2 import JobStatusTypeV2
33
+ from fractal_server.app.schemas.v2 import ResourceType
32
34
  from fractal_server.config import get_settings
33
35
  from fractal_server.logger import set_logger
34
36
  from fractal_server.runner.set_start_and_last_task_index import (
@@ -122,10 +124,14 @@ async def apply_workflow(
122
124
  )
123
125
  used_task_group_ids.add(task.taskgroupv2_id)
124
126
 
127
+ # Get validated resource and profile
128
+ resource, profile = await validate_user_profile(
129
+ user=user,
130
+ db=db,
131
+ )
125
132
  # Validate user settings
126
- FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
127
133
  user_settings = await validate_user_settings(
128
- user=user, backend=FRACTAL_RUNNER_BACKEND, db=db
134
+ user=user, backend=resource.type, db=db
129
135
  )
130
136
  # Check that no other job with the same dataset_id is SUBMITTED
131
137
  stm = (
@@ -157,11 +163,11 @@ async def apply_workflow(
157
163
  job_create.slurm_account = user_settings.slurm_accounts[0]
158
164
 
159
165
  # User appropriate FractalSSH object
160
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
166
+ if resource.type == ResourceType.SLURM_SSH:
161
167
  ssh_config = dict(
162
- user=user_settings.ssh_username,
163
- host=user_settings.ssh_host,
164
- key_path=user_settings.ssh_private_key_path,
168
+ user=profile.username,
169
+ host=resource.host,
170
+ key_path=profile.ssh_key_path,
165
171
  )
166
172
  fractal_ssh_list = request.app.state.fractal_ssh_list
167
173
  try:
@@ -193,7 +199,7 @@ async def apply_workflow(
193
199
  workflow.model_dump_json(exclude={"task_list"})
194
200
  ),
195
201
  project_dump=json.loads(
196
- project.model_dump_json(exclude={"user_list"})
202
+ project.model_dump_json(exclude={"user_list", "resource_id"})
197
203
  ),
198
204
  **job_create.model_dump(),
199
205
  )
@@ -214,26 +220,28 @@ async def apply_workflow(
214
220
 
215
221
  # Define server-side job directory
216
222
  timestamp_string = job.start_timestamp.strftime("%Y%m%d_%H%M%S")
217
- WORKFLOW_DIR_LOCAL = settings.FRACTAL_RUNNER_WORKING_BASE_DIR / (
223
+ WORKFLOW_DIR_LOCAL = Path(resource.jobs_local_dir) / (
218
224
  f"proj_v2_{project_id:07d}_wf_{workflow_id:07d}_job_{job.id:07d}"
219
225
  f"_{timestamp_string}"
220
226
  )
221
227
 
222
- cache_dir = (
223
- Path(user_settings.project_dir) / ".fractal_cache"
224
- if user_settings.project_dir is not None
225
- else None
226
- )
227
-
228
228
  # Define user-side job directory
229
- if FRACTAL_RUNNER_BACKEND == "local":
230
- WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
231
- elif FRACTAL_RUNNER_BACKEND == "slurm":
232
- WORKFLOW_DIR_REMOTE = cache_dir / WORKFLOW_DIR_LOCAL.name
233
- elif FRACTAL_RUNNER_BACKEND == "slurm_ssh":
234
- WORKFLOW_DIR_REMOTE = (
235
- Path(user_settings.ssh_jobs_dir) / WORKFLOW_DIR_LOCAL.name
236
- )
229
+ match resource.type:
230
+ case ResourceType.LOCAL:
231
+ WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
232
+ cache_dir = None
233
+ case ResourceType.SLURM_SUDO:
234
+ cache_dir = (
235
+ Path(user_settings.project_dir) / ".fractal_cache"
236
+ if user_settings.project_dir is not None
237
+ else None
238
+ )
239
+ WORKFLOW_DIR_REMOTE = cache_dir / WORKFLOW_DIR_LOCAL.name
240
+ case ResourceType.SLURM_SSH:
241
+ WORKFLOW_DIR_REMOTE = (
242
+ Path(profile.jobs_remote_dir) / WORKFLOW_DIR_LOCAL.name
243
+ )
244
+ cache_dir = None
237
245
 
238
246
  # Update job folders in the db
239
247
  job.working_dir = WORKFLOW_DIR_LOCAL.as_posix()
@@ -250,11 +258,11 @@ async def apply_workflow(
250
258
  dataset_id=dataset.id,
251
259
  job_id=job.id,
252
260
  user_id=user.id,
253
- user_settings=user_settings,
254
261
  worker_init=job.worker_init,
255
- slurm_user=user_settings.slurm_user,
256
262
  user_cache_dir=cache_dir.as_posix() if cache_dir else None,
257
263
  fractal_ssh=fractal_ssh,
264
+ resource=resource,
265
+ profile=profile,
258
266
  )
259
267
  request.app.state.jobsV2.append(job.id)
260
268
  logger.info(
@@ -9,6 +9,7 @@ from sqlmodel import func
9
9
  from sqlmodel import or_
10
10
  from sqlmodel import select
11
11
 
12
+ from ._aux_functions import _get_resource_and_profile_ids
12
13
  from ._aux_functions_tasks import _get_task_full_access
13
14
  from ._aux_functions_tasks import _get_task_read_access
14
15
  from ._aux_functions_tasks import _get_valid_user_group_id
@@ -187,9 +188,13 @@ async def create_task(
187
188
  user_group_id=user_group_id,
188
189
  version=db_task.version,
189
190
  )
191
+ resource_id, _ = await _get_resource_and_profile_ids(
192
+ user_id=user.id, db=db
193
+ )
190
194
  db_task_group = TaskGroupV2(
191
195
  user_id=user.id,
192
196
  user_group_id=user_group_id,
197
+ resource_id=resource_id,
193
198
  active=True,
194
199
  task_list=[db_task],
195
200
  origin=TaskGroupV2OriginEnum.OTHER,
@@ -14,10 +14,8 @@ from pydantic import BaseModel
14
14
  from pydantic import model_validator
15
15
  from pydantic import ValidationError
16
16
 
17
- from .....config import get_settings
18
17
  from .....logger import reset_logger_handlers
19
18
  from .....logger import set_logger
20
- from .....syringe import Inject
21
19
  from ....db import AsyncSession
22
20
  from ....db import get_async_db
23
21
  from ....models.v2 import TaskGroupV2
@@ -26,7 +24,8 @@ from ....schemas.v2 import TaskCollectPipV2
26
24
  from ....schemas.v2 import TaskGroupActivityStatusV2
27
25
  from ....schemas.v2 import TaskGroupActivityV2Read
28
26
  from ....schemas.v2 import TaskGroupCreateV2Strict
29
- from ...aux.validate_user_settings import validate_user_settings
27
+ from ...aux.validate_user_profile import validate_user_profile
28
+ from ._aux_functions import _get_resource_and_profile_ids
30
29
  from ._aux_functions_task_lifecycle import get_package_version_from_pypi
31
30
  from ._aux_functions_tasks import _get_valid_user_group_id
32
31
  from ._aux_functions_tasks import _verify_non_duplication_group_constraint
@@ -35,11 +34,11 @@ from ._aux_functions_tasks import _verify_non_duplication_user_constraint
35
34
  from fractal_server.app.models import UserOAuth
36
35
  from fractal_server.app.models.v2 import TaskGroupActivityV2
37
36
  from fractal_server.app.routes.auth import current_active_verified_user
37
+ from fractal_server.app.schemas.v2 import ResourceType
38
38
  from fractal_server.app.schemas.v2 import (
39
39
  TaskGroupActivityActionV2,
40
40
  )
41
41
  from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
42
- from fractal_server.ssh._fabric import SSHConfig
43
42
  from fractal_server.tasks.v2.local.collect import (
44
43
  collect_local,
45
44
  )
@@ -47,7 +46,7 @@ from fractal_server.tasks.v2.ssh import collect_ssh
47
46
  from fractal_server.tasks.v2.utils_package_names import _parse_wheel_filename
48
47
  from fractal_server.tasks.v2.utils_package_names import normalize_package_name
49
48
  from fractal_server.tasks.v2.utils_python_interpreter import (
50
- get_python_interpreter_v2,
49
+ get_python_interpreter,
51
50
  )
52
51
 
53
52
 
@@ -168,12 +167,19 @@ async def collect_tasks_pip(
168
167
  """
169
168
  Task-collection endpoint
170
169
  """
171
- # Get settings
172
- settings = Inject(get_settings)
173
170
 
171
+ # Get validated resource and profile
172
+ resource, profile = await validate_user_profile(
173
+ user=user,
174
+ db=db,
175
+ )
174
176
  # Get some validated request data
175
177
  task_collect = request_data.task_collect
176
178
 
179
+ resource_id, _ = await _get_resource_and_profile_ids(
180
+ user_id=user.id, db=db
181
+ )
182
+
177
183
  # Initialize task-group attributes
178
184
  task_group_attrs = dict(
179
185
  user_id=user.id,
@@ -182,14 +188,15 @@ async def collect_tasks_pip(
182
188
 
183
189
  # Set/check python version
184
190
  if task_collect.python_version is None:
185
- task_group_attrs[
186
- "python_version"
187
- ] = settings.FRACTAL_TASKS_PYTHON_DEFAULT_VERSION
191
+ task_group_attrs["python_version"] = resource.tasks_python_config[
192
+ "default_version"
193
+ ]
188
194
  else:
189
195
  task_group_attrs["python_version"] = task_collect.python_version
190
196
  try:
191
- get_python_interpreter_v2(
192
- python_version=task_group_attrs["python_version"]
197
+ get_python_interpreter(
198
+ python_version=task_group_attrs["python_version"],
199
+ resource=resource,
193
200
  )
194
201
  except ValueError:
195
202
  raise HTTPException(
@@ -259,16 +266,11 @@ async def collect_tasks_pip(
259
266
  # Set user_group_id
260
267
  task_group_attrs["user_group_id"] = user_group_id
261
268
 
262
- # Validate user settings (backend-specific)
263
- user_settings = await validate_user_settings(
264
- user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
265
- )
266
-
267
269
  # Set path and venv_path
268
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
269
- base_tasks_path = user_settings.ssh_tasks_dir
270
+ if resource.type == ResourceType.SLURM_SSH:
271
+ base_tasks_path = profile.tasks_remote_dir
270
272
  else:
271
- base_tasks_path = settings.FRACTAL_TASKS_DIR.as_posix()
273
+ base_tasks_path = resource.tasks_local_dir
272
274
  task_group_path = (
273
275
  Path(base_tasks_path)
274
276
  / str(user.id)
@@ -309,7 +311,7 @@ async def collect_tasks_pip(
309
311
 
310
312
  # On-disk checks
311
313
 
312
- if settings.FRACTAL_RUNNER_BACKEND != "slurm_ssh":
314
+ if resource.type != ResourceType.SLURM_SSH:
313
315
  # Verify that folder does not exist (for local collection)
314
316
  if Path(task_group_path).exists():
315
317
  raise HTTPException(
@@ -318,7 +320,7 @@ async def collect_tasks_pip(
318
320
  )
319
321
 
320
322
  # Create TaskGroupV2 object
321
- task_group = TaskGroupV2(**task_group_attrs)
323
+ task_group = TaskGroupV2(**task_group_attrs, resource_id=resource_id)
322
324
  db.add(task_group)
323
325
  await db.commit()
324
326
  await db.refresh(task_group)
@@ -340,33 +342,20 @@ async def collect_tasks_pip(
340
342
 
341
343
  # END of SSH/non-SSH common part
342
344
 
343
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
344
- # SSH task collection
345
- # Use appropriate FractalSSH object
346
- ssh_config = SSHConfig(
347
- user=user_settings.ssh_username,
348
- host=user_settings.ssh_host,
349
- key_path=user_settings.ssh_private_key_path,
350
- )
351
-
352
- background_tasks.add_task(
353
- collect_ssh,
354
- task_group_id=task_group.id,
355
- task_group_activity_id=task_group_activity.id,
356
- ssh_config=ssh_config,
357
- tasks_base_dir=user_settings.ssh_tasks_dir,
358
- wheel_file=wheel_file,
359
- )
360
-
345
+ if resource.type == ResourceType.SLURM_SSH:
346
+ collect_function = collect_ssh
361
347
  else:
362
- # Local task collection
348
+ collect_function = collect_local
349
+
350
+ background_tasks.add_task(
351
+ collect_function,
352
+ task_group_id=task_group.id,
353
+ task_group_activity_id=task_group_activity.id,
354
+ wheel_file=wheel_file,
355
+ resource=resource,
356
+ profile=profile,
357
+ )
363
358
 
364
- background_tasks.add_task(
365
- collect_local,
366
- task_group_id=task_group.id,
367
- task_group_activity_id=task_group_activity.id,
368
- wheel_file=wheel_file,
369
- )
370
359
  logger.debug(
371
360
  "Task-collection endpoint: start background collection "
372
361
  "and return task_group_activity"
@@ -9,6 +9,8 @@ from fastapi import HTTPException
9
9
  from fastapi import status
10
10
  from sqlalchemy.ext.asyncio import AsyncSession
11
11
 
12
+ from ...aux.validate_user_profile import validate_user_profile
13
+ from ._aux_functions import _get_resource_and_profile_ids
12
14
  from ._aux_functions_tasks import _get_valid_user_group_id
13
15
  from ._aux_functions_tasks import _verify_non_duplication_group_constraint
14
16
  from ._aux_functions_tasks import _verify_non_duplication_user_constraint
@@ -16,15 +18,14 @@ from fractal_server.app.db import get_async_db
16
18
  from fractal_server.app.models import UserOAuth
17
19
  from fractal_server.app.models.v2 import TaskGroupV2
18
20
  from fractal_server.app.routes.auth import current_active_verified_user
21
+ from fractal_server.app.schemas.v2 import ResourceType
19
22
  from fractal_server.app.schemas.v2 import TaskCollectCustomV2
20
23
  from fractal_server.app.schemas.v2 import TaskCreateV2
21
24
  from fractal_server.app.schemas.v2 import TaskGroupCreateV2
22
25
  from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
23
26
  from fractal_server.app.schemas.v2 import TaskReadV2
24
- from fractal_server.config import get_settings
25
27
  from fractal_server.logger import set_logger
26
28
  from fractal_server.string_tools import validate_cmd
27
- from fractal_server.syringe import Inject
28
29
  from fractal_server.tasks.v2.utils_background import (
29
30
  prepare_tasks_metadata,
30
31
  )
@@ -47,7 +48,8 @@ async def collect_task_custom(
47
48
  user: UserOAuth = Depends(current_active_verified_user),
48
49
  db: AsyncSession = Depends(get_async_db),
49
50
  ) -> list[TaskReadV2]:
50
- settings = Inject(get_settings)
51
+ # Get validated resource and profile
52
+ resource, profile = await validate_user_profile(user=user, db=db)
51
53
 
52
54
  # Validate query parameters related to user-group ownership
53
55
  user_group_id = await _get_valid_user_group_id(
@@ -57,7 +59,7 @@ async def collect_task_custom(
57
59
  db=db,
58
60
  )
59
61
 
60
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
62
+ if resource.type == ResourceType.SLURM_SSH:
61
63
  if task_collect.package_root is None:
62
64
  raise HTTPException(
63
65
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
@@ -143,6 +145,10 @@ async def collect_task_custom(
143
145
  package_version=task_collect.version,
144
146
  )
145
147
 
148
+ resource_id, _ = await _get_resource_and_profile_ids(
149
+ user_id=user.id, db=db
150
+ )
151
+
146
152
  # Prepare task-group attributes
147
153
  task_group_attrs = dict(
148
154
  origin=TaskGroupV2OriginEnum.OTHER,
@@ -167,7 +173,7 @@ async def collect_task_custom(
167
173
  db=db,
168
174
  )
169
175
 
170
- task_group = TaskGroupV2(**task_group_attrs)
176
+ task_group = TaskGroupV2(**task_group_attrs, resource_id=resource_id)
171
177
  db.add(task_group)
172
178
  await db.commit()
173
179
  await db.refresh(task_group)
@@ -10,6 +10,7 @@ from fastapi import Response
10
10
  from fastapi import status
11
11
  from fastapi import UploadFile
12
12
 
13
+ from ._aux_functions import _get_resource_and_profile_ids
13
14
  from fractal_server.app.db import AsyncSession
14
15
  from fractal_server.app.db import get_async_db
15
16
  from fractal_server.app.models import UserOAuth
@@ -28,18 +29,16 @@ from fractal_server.app.routes.api.v2._aux_functions_tasks import (
28
29
  _verify_non_duplication_user_constraint,
29
30
  )
30
31
  from fractal_server.app.routes.auth import current_active_verified_user
31
- from fractal_server.app.routes.aux.validate_user_settings import (
32
- validate_user_settings,
32
+ from fractal_server.app.routes.aux.validate_user_profile import (
33
+ validate_user_profile,
33
34
  )
34
35
  from fractal_server.app.schemas.v2 import FractalUploadedFile
36
+ from fractal_server.app.schemas.v2 import ResourceType
35
37
  from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
36
38
  from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
37
39
  from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
38
40
  from fractal_server.app.schemas.v2.task_group import TaskGroupV2OriginEnum
39
- from fractal_server.config import get_settings
40
41
  from fractal_server.logger import set_logger
41
- from fractal_server.ssh._fabric import SSHConfig
42
- from fractal_server.syringe import Inject
43
42
  from fractal_server.tasks.v2.local import collect_local_pixi
44
43
  from fractal_server.tasks.v2.ssh import collect_ssh_pixi
45
44
  from fractal_server.tasks.v2.utils_package_names import normalize_package_name
@@ -89,23 +88,26 @@ async def collect_task_pixi(
89
88
  user: UserOAuth = Depends(current_active_verified_user),
90
89
  db: AsyncSession = Depends(get_async_db),
91
90
  ) -> TaskGroupActivityV2Read:
92
- settings = Inject(get_settings)
91
+ # Get validated resource and profile
92
+ resource, profile = await validate_user_profile(user=user, db=db)
93
+
93
94
  # Check if Pixi is available
94
- if settings.pixi is None:
95
+ if not resource.tasks_pixi_config:
95
96
  raise HTTPException(
96
97
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
97
98
  detail="Pixi task collection is not available.",
98
99
  )
99
100
  # Check if provided Pixi version is available. Use default if not provided
100
101
  if pixi_version is None:
101
- pixi_version = settings.pixi.default_version
102
+ pixi_version = resource.tasks_pixi_config["default_version"]
102
103
  else:
103
- if pixi_version not in settings.pixi.versions:
104
+ if pixi_version not in resource.tasks_pixi_config["versions"]:
104
105
  raise HTTPException(
105
106
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
106
107
  detail=(
107
- f"Pixi version {pixi_version} is not available. Available "
108
- f"versions: {list(settings.pixi.versions.keys())}"
108
+ f"Pixi version '{pixi_version}' is not available. "
109
+ "Available versions: "
110
+ f"{list(resource.tasks_pixi_config['versions'].keys())}"
109
111
  ),
110
112
  )
111
113
 
@@ -123,21 +125,22 @@ async def collect_task_pixi(
123
125
  db=db,
124
126
  )
125
127
 
126
- user_settings = await validate_user_settings(
127
- user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
128
- )
129
-
130
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
131
- base_tasks_path = user_settings.ssh_tasks_dir
128
+ if resource.type == ResourceType.SLURM_SSH:
129
+ base_tasks_path = profile.tasks_remote_dir
132
130
  else:
133
- base_tasks_path = settings.FRACTAL_TASKS_DIR.as_posix()
131
+ base_tasks_path = resource.tasks_local_dir
134
132
  task_group_path = (
135
133
  Path(base_tasks_path) / str(user.id) / pkg_name / version
136
134
  ).as_posix()
137
135
 
136
+ resource_id, _ = await _get_resource_and_profile_ids(
137
+ user_id=user.id, db=db
138
+ )
139
+
138
140
  task_group_attrs = dict(
139
141
  user_id=user.id,
140
142
  user_group_id=user_group_id,
143
+ resource_id=resource_id,
141
144
  origin=TaskGroupV2OriginEnum.PIXI,
142
145
  pixi_version=pixi_version,
143
146
  pkg_name=pkg_name,
@@ -162,7 +165,7 @@ async def collect_task_pixi(
162
165
  db=db,
163
166
  )
164
167
 
165
- if settings.FRACTAL_RUNNER_BACKEND != "slurm_ssh":
168
+ if resource.type != ResourceType.SLURM_SSH:
166
169
  if Path(task_group_path).exists():
167
170
  raise HTTPException(
168
171
  status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
@@ -187,28 +190,19 @@ async def collect_task_pixi(
187
190
  await db.commit()
188
191
  await db.refresh(task_group_activity)
189
192
 
190
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
191
- ssh_config = SSHConfig(
192
- user=user_settings.ssh_username,
193
- host=user_settings.ssh_host,
194
- key_path=user_settings.ssh_private_key_path,
195
- )
196
-
197
- background_tasks.add_task(
198
- collect_ssh_pixi,
199
- task_group_id=task_group.id,
200
- task_group_activity_id=task_group_activity.id,
201
- ssh_config=ssh_config,
202
- tasks_base_dir=user_settings.ssh_tasks_dir,
203
- tar_gz_file=tar_gz_file,
204
- )
193
+ if resource.type == ResourceType.SLURM_SSH:
194
+ collect_function = collect_ssh_pixi
205
195
  else:
206
- background_tasks.add_task(
207
- collect_local_pixi,
208
- task_group_id=task_group.id,
209
- task_group_activity_id=task_group_activity.id,
210
- tar_gz_file=tar_gz_file,
211
- )
196
+ collect_function = collect_local_pixi
197
+
198
+ background_tasks.add_task(
199
+ collect_function,
200
+ task_group_id=task_group.id,
201
+ task_group_activity_id=task_group_activity.id,
202
+ tar_gz_file=tar_gz_file,
203
+ resource=resource,
204
+ profile=profile,
205
+ )
212
206
  logger.info(
213
207
  "Task-collection endpoint: start background collection "
214
208
  "and return task_group_activity. "