fractal-server 2.14.5__py3-none-any.whl → 2.14.7__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 (108) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/db/__init__.py +2 -2
  3. fractal_server/app/models/security.py +8 -8
  4. fractal_server/app/models/user_settings.py +8 -10
  5. fractal_server/app/models/v2/accounting.py +2 -3
  6. fractal_server/app/models/v2/dataset.py +1 -2
  7. fractal_server/app/models/v2/history.py +3 -4
  8. fractal_server/app/models/v2/job.py +10 -11
  9. fractal_server/app/models/v2/project.py +1 -2
  10. fractal_server/app/models/v2/task.py +13 -14
  11. fractal_server/app/models/v2/task_group.py +15 -16
  12. fractal_server/app/models/v2/workflow.py +1 -2
  13. fractal_server/app/models/v2/workflowtask.py +6 -7
  14. fractal_server/app/routes/admin/v2/accounting.py +3 -4
  15. fractal_server/app/routes/admin/v2/job.py +13 -14
  16. fractal_server/app/routes/admin/v2/project.py +2 -4
  17. fractal_server/app/routes/admin/v2/task.py +11 -13
  18. fractal_server/app/routes/admin/v2/task_group.py +15 -17
  19. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +5 -8
  20. fractal_server/app/routes/api/v2/__init__.py +2 -0
  21. fractal_server/app/routes/api/v2/_aux_functions.py +7 -9
  22. fractal_server/app/routes/api/v2/_aux_functions_history.py +1 -1
  23. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +1 -3
  24. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +5 -6
  25. fractal_server/app/routes/api/v2/dataset.py +6 -8
  26. fractal_server/app/routes/api/v2/history.py +5 -8
  27. fractal_server/app/routes/api/v2/images.py +2 -3
  28. fractal_server/app/routes/api/v2/job.py +5 -6
  29. fractal_server/app/routes/api/v2/pre_submission_checks.py +1 -3
  30. fractal_server/app/routes/api/v2/project.py +2 -4
  31. fractal_server/app/routes/api/v2/status_legacy.py +2 -4
  32. fractal_server/app/routes/api/v2/submit.py +3 -4
  33. fractal_server/app/routes/api/v2/task.py +6 -7
  34. fractal_server/app/routes/api/v2/task_collection.py +11 -13
  35. fractal_server/app/routes/api/v2/task_collection_custom.py +4 -4
  36. fractal_server/app/routes/api/v2/task_group.py +6 -8
  37. fractal_server/app/routes/api/v2/task_group_lifecycle.py +6 -9
  38. fractal_server/app/routes/api/v2/task_version_update.py +270 -0
  39. fractal_server/app/routes/api/v2/workflow.py +5 -6
  40. fractal_server/app/routes/api/v2/workflow_import.py +3 -5
  41. fractal_server/app/routes/api/v2/workflowtask.py +2 -114
  42. fractal_server/app/routes/auth/current_user.py +2 -2
  43. fractal_server/app/routes/pagination.py +2 -3
  44. fractal_server/app/runner/exceptions.py +15 -16
  45. fractal_server/app/runner/executors/base_runner.py +3 -3
  46. fractal_server/app/runner/executors/call_command_wrapper.py +1 -1
  47. fractal_server/app/runner/executors/local/get_local_config.py +2 -3
  48. fractal_server/app/runner/executors/local/runner.py +1 -1
  49. fractal_server/app/runner/executors/slurm_common/_batching.py +2 -3
  50. fractal_server/app/runner/executors/slurm_common/_slurm_config.py +27 -29
  51. fractal_server/app/runner/executors/slurm_common/base_slurm_runner.py +32 -14
  52. fractal_server/app/runner/executors/slurm_common/get_slurm_config.py +2 -3
  53. fractal_server/app/runner/executors/slurm_common/remote.py +2 -2
  54. fractal_server/app/runner/executors/slurm_common/slurm_job_task_models.py +2 -3
  55. fractal_server/app/runner/executors/slurm_ssh/run_subprocess.py +2 -3
  56. fractal_server/app/runner/executors/slurm_ssh/runner.py +5 -4
  57. fractal_server/app/runner/executors/slurm_sudo/_subprocess_run_as_user.py +1 -2
  58. fractal_server/app/runner/executors/slurm_sudo/runner.py +7 -8
  59. fractal_server/app/runner/set_start_and_last_task_index.py +2 -5
  60. fractal_server/app/runner/shutdown.py +5 -11
  61. fractal_server/app/runner/task_files.py +3 -5
  62. fractal_server/app/runner/v2/_local.py +3 -4
  63. fractal_server/app/runner/v2/_slurm_ssh.py +8 -7
  64. fractal_server/app/runner/v2/_slurm_sudo.py +8 -9
  65. fractal_server/app/runner/v2/runner.py +4 -5
  66. fractal_server/app/runner/v2/runner_functions.py +4 -5
  67. fractal_server/app/runner/v2/submit_workflow.py +12 -11
  68. fractal_server/app/runner/v2/task_interface.py +2 -3
  69. fractal_server/app/runner/versions.py +1 -2
  70. fractal_server/app/schemas/user.py +2 -4
  71. fractal_server/app/schemas/user_group.py +1 -2
  72. fractal_server/app/schemas/user_settings.py +19 -21
  73. fractal_server/app/schemas/v2/dataset.py +2 -3
  74. fractal_server/app/schemas/v2/dumps.py +13 -15
  75. fractal_server/app/schemas/v2/history.py +6 -7
  76. fractal_server/app/schemas/v2/job.py +17 -18
  77. fractal_server/app/schemas/v2/manifest.py +12 -13
  78. fractal_server/app/schemas/v2/status_legacy.py +2 -2
  79. fractal_server/app/schemas/v2/task.py +29 -30
  80. fractal_server/app/schemas/v2/task_collection.py +8 -9
  81. fractal_server/app/schemas/v2/task_group.py +22 -23
  82. fractal_server/app/schemas/v2/workflow.py +1 -2
  83. fractal_server/app/schemas/v2/workflowtask.py +27 -29
  84. fractal_server/app/security/__init__.py +10 -12
  85. fractal_server/config.py +32 -33
  86. fractal_server/images/models.py +2 -4
  87. fractal_server/images/tools.py +4 -7
  88. fractal_server/logger.py +3 -5
  89. fractal_server/ssh/_fabric.py +37 -12
  90. fractal_server/string_tools.py +2 -2
  91. fractal_server/syringe.py +1 -1
  92. fractal_server/tasks/v2/local/collect.py +2 -3
  93. fractal_server/tasks/v2/local/deactivate.py +1 -1
  94. fractal_server/tasks/v2/local/reactivate.py +1 -1
  95. fractal_server/tasks/v2/ssh/collect.py +256 -245
  96. fractal_server/tasks/v2/ssh/deactivate.py +210 -187
  97. fractal_server/tasks/v2/ssh/reactivate.py +154 -146
  98. fractal_server/tasks/v2/utils_background.py +2 -3
  99. fractal_server/types/__init__.py +1 -2
  100. fractal_server/types/validators/_filter_validators.py +1 -2
  101. fractal_server/utils.py +4 -5
  102. fractal_server/zip_tools.py +1 -1
  103. {fractal_server-2.14.5.dist-info → fractal_server-2.14.7.dist-info}/METADATA +2 -3
  104. {fractal_server-2.14.5.dist-info → fractal_server-2.14.7.dist-info}/RECORD +107 -107
  105. fractal_server/app/history/__init__.py +0 -0
  106. {fractal_server-2.14.5.dist-info → fractal_server-2.14.7.dist-info}/LICENSE +0 -0
  107. {fractal_server-2.14.5.dist-info → fractal_server-2.14.7.dist-info}/WHEEL +0 -0
  108. {fractal_server-2.14.5.dist-info → fractal_server-2.14.7.dist-info}/entry_points.txt +0 -0
@@ -30,6 +30,7 @@ from fractal_server.app.schemas.v2 import TaskGroupReadV2
30
30
  from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
31
31
  from fractal_server.config import get_settings
32
32
  from fractal_server.logger import set_logger
33
+ from fractal_server.ssh._fabric import SSHConfig
33
34
  from fractal_server.syringe import Inject
34
35
  from fractal_server.tasks.v2.local import deactivate_local
35
36
  from fractal_server.tasks.v2.local import reactivate_local
@@ -120,19 +121,17 @@ async def deactivate_task_group(
120
121
  user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
121
122
  )
122
123
  # User appropriate FractalSSH object
123
- ssh_credentials = dict(
124
+ ssh_config = SSHConfig(
124
125
  user=user_settings.ssh_username,
125
126
  host=user_settings.ssh_host,
126
127
  key_path=user_settings.ssh_private_key_path,
127
128
  )
128
- fractal_ssh_list = request.app.state.fractal_ssh_list
129
- fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
130
129
 
131
130
  background_tasks.add_task(
132
131
  deactivate_ssh,
133
132
  task_group_id=task_group.id,
134
133
  task_group_activity_id=task_group_activity.id,
135
- fractal_ssh=fractal_ssh,
134
+ ssh_config=ssh_config,
136
135
  tasks_base_dir=user_settings.ssh_tasks_dir,
137
136
  )
138
137
  else:
@@ -238,19 +237,17 @@ async def reactivate_task_group(
238
237
  user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
239
238
  )
240
239
  # Use appropriate FractalSSH object
241
- ssh_credentials = dict(
240
+ ssh_config = SSHConfig(
242
241
  user=user_settings.ssh_username,
243
242
  host=user_settings.ssh_host,
244
243
  key_path=user_settings.ssh_private_key_path,
245
244
  )
246
- fractal_ssh_list = request.app.state.fractal_ssh_list
247
- fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
248
245
 
249
246
  background_tasks.add_task(
250
247
  reactivate_ssh,
251
248
  task_group_id=task_group.id,
252
249
  task_group_activity_id=task_group_activity.id,
253
- fractal_ssh=fractal_ssh,
250
+ ssh_config=ssh_config,
254
251
  tasks_base_dir=user_settings.ssh_tasks_dir,
255
252
  )
256
253
  else:
@@ -16,6 +16,7 @@ from .task_collection import router as task_collection_router_v2
16
16
  from .task_collection_custom import router as task_collection_router_v2_custom
17
17
  from .task_group import router as task_group_router_v2
18
18
  from .task_group_lifecycle import router as task_group_lifecycle_router_v2
19
+ from .task_version_update import router as task_version_update_router_v2
19
20
  from .workflow import router as workflow_router_v2
20
21
  from .workflow_import import router as workflow_import_router_v2
21
22
  from .workflowtask import router as workflowtask_router_v2
@@ -55,6 +56,7 @@ router_api_v2.include_router(
55
56
  )
56
57
 
57
58
  router_api_v2.include_router(task_router_v2, prefix="/task", tags=["V2 Task"])
59
+ router_api_v2.include_router(task_version_update_router_v2, tags=["V2 Task"])
58
60
  router_api_v2.include_router(
59
61
  task_group_router_v2, prefix="/task-group", tags=["V2 TaskGroup"]
60
62
  )
@@ -3,8 +3,6 @@ Auxiliary functions to get object from the database or perform simple checks
3
3
  """
4
4
  from typing import Any
5
5
  from typing import Literal
6
- from typing import Optional
7
- from typing import Union
8
6
 
9
7
  from fastapi import HTTPException
10
8
  from fastapi import status
@@ -233,7 +231,7 @@ async def _get_dataset_check_owner(
233
231
  dataset_id: int,
234
232
  user_id: int,
235
233
  db: AsyncSession,
236
- ) -> dict[Literal["dataset", "project"], Union[DatasetV2, ProjectV2]]:
234
+ ) -> dict[Literal["dataset", "project"], DatasetV2 | ProjectV2]:
237
235
  """
238
236
  Get a dataset and a project, after access control on the project
239
237
 
@@ -279,7 +277,7 @@ async def _get_job_check_owner(
279
277
  job_id: int,
280
278
  user_id: int,
281
279
  db: AsyncSession,
282
- ) -> dict[Literal["job", "project"], Union[JobV2, ProjectV2]]:
280
+ ) -> dict[Literal["job", "project"], JobV2 | ProjectV2]:
283
281
  """
284
282
  Get a job and a project, after access control on the project
285
283
 
@@ -331,11 +329,11 @@ async def _workflow_insert_task(
331
329
  *,
332
330
  workflow_id: int,
333
331
  task_id: int,
334
- meta_parallel: Optional[dict[str, Any]] = None,
335
- meta_non_parallel: Optional[dict[str, Any]] = None,
336
- args_non_parallel: Optional[dict[str, Any]] = None,
337
- args_parallel: Optional[dict[str, Any]] = None,
338
- type_filters: Optional[dict[str, bool]] = None,
332
+ meta_parallel: dict[str, Any] | None = None,
333
+ meta_non_parallel: dict[str, Any] | None = None,
334
+ args_non_parallel: dict[str, Any] | None = None,
335
+ args_parallel: dict[str, Any] | None = None,
336
+ type_filters: dict[str, bool] | None = None,
339
337
  db: AsyncSession,
340
338
  ) -> WorkflowTaskV2:
341
339
  """
@@ -81,7 +81,7 @@ def read_log_file(
81
81
  )
82
82
 
83
83
  try:
84
- with open(logfile, "r") as f:
84
+ with open(logfile) as f:
85
85
  return f.read()
86
86
  except Exception as e:
87
87
  return (
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from fastapi import HTTPException
4
2
  from fastapi import status
5
3
  from httpx import AsyncClient
@@ -23,7 +21,7 @@ logger = set_logger(__name__)
23
21
 
24
22
  async def get_package_version_from_pypi(
25
23
  name: str,
26
- version: Optional[str] = None,
24
+ version: str | None = None,
27
25
  ) -> str:
28
26
  """
29
27
  Make a GET call to PyPI JSON API and get latest *compatible* version.
@@ -3,7 +3,6 @@ Auxiliary functions to get task and task-group object from the database or
3
3
  perform simple checks
4
4
  """
5
5
  from typing import Any
6
- from typing import Optional
7
6
 
8
7
  from fastapi import HTTPException
9
8
  from fastapi import status
@@ -191,11 +190,11 @@ async def _get_task_read_access(
191
190
 
192
191
  async def _get_valid_user_group_id(
193
192
  *,
194
- user_group_id: Optional[int] = None,
193
+ user_group_id: int | None = None,
195
194
  private: bool,
196
195
  user_id: int,
197
196
  db: AsyncSession,
198
- ) -> Optional[int]:
197
+ ) -> int | None:
199
198
  """
200
199
  Validate query parameters for endpoints that create some task(s).
201
200
 
@@ -257,7 +256,7 @@ async def _verify_non_duplication_user_constraint(
257
256
  db: AsyncSession,
258
257
  user_id: int,
259
258
  pkg_name: str,
260
- version: Optional[str],
259
+ version: str | None,
261
260
  ):
262
261
  stm = (
263
262
  select(TaskGroupV2)
@@ -294,9 +293,9 @@ async def _verify_non_duplication_user_constraint(
294
293
 
295
294
  async def _verify_non_duplication_group_constraint(
296
295
  db: AsyncSession,
297
- user_group_id: Optional[int],
296
+ user_group_id: int | None,
298
297
  pkg_name: str,
299
- version: Optional[str],
298
+ version: str | None,
300
299
  ):
301
300
  if user_group_id is None:
302
301
  return
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from fastapi import APIRouter
4
2
  from fastapi import Depends
5
3
  from fastapi import HTTPException
@@ -38,7 +36,7 @@ async def create_dataset(
38
36
  dataset: DatasetCreateV2,
39
37
  user: UserOAuth = Depends(current_active_user),
40
38
  db: AsyncSession = Depends(get_async_db),
41
- ) -> Optional[DatasetReadV2]:
39
+ ) -> DatasetReadV2 | None:
42
40
  """
43
41
  Add new dataset to current project
44
42
  """
@@ -92,7 +90,7 @@ async def read_dataset_list(
92
90
  project_id: int,
93
91
  user: UserOAuth = Depends(current_active_user),
94
92
  db: AsyncSession = Depends(get_async_db),
95
- ) -> Optional[list[DatasetReadV2]]:
93
+ ) -> list[DatasetReadV2] | None:
96
94
  """
97
95
  Get dataset list for given project
98
96
  """
@@ -120,7 +118,7 @@ async def read_dataset(
120
118
  dataset_id: int,
121
119
  user: UserOAuth = Depends(current_active_user),
122
120
  db: AsyncSession = Depends(get_async_db),
123
- ) -> Optional[DatasetReadV2]:
121
+ ) -> DatasetReadV2 | None:
124
122
  """
125
123
  Get info on a dataset associated to the current project
126
124
  """
@@ -145,7 +143,7 @@ async def update_dataset(
145
143
  dataset_update: DatasetUpdateV2,
146
144
  user: UserOAuth = Depends(current_active_user),
147
145
  db: AsyncSession = Depends(get_async_db),
148
- ) -> Optional[DatasetReadV2]:
146
+ ) -> DatasetReadV2 | None:
149
147
  """
150
148
  Edit a dataset associated to the current project
151
149
  """
@@ -247,7 +245,7 @@ async def export_dataset(
247
245
  dataset_id: int,
248
246
  user: UserOAuth = Depends(current_active_user),
249
247
  db: AsyncSession = Depends(get_async_db),
250
- ) -> Optional[DatasetExportV2]:
248
+ ) -> DatasetExportV2 | None:
251
249
  """
252
250
  Export an existing dataset
253
251
  """
@@ -274,7 +272,7 @@ async def import_dataset(
274
272
  dataset: DatasetImportV2,
275
273
  user: UserOAuth = Depends(current_active_user),
276
274
  db: AsyncSession = Depends(get_async_db),
277
- ) -> Optional[DatasetReadV2]:
275
+ ) -> DatasetReadV2 | None:
278
276
  """
279
277
  Import an existing dataset into a project
280
278
  """
@@ -1,6 +1,5 @@
1
1
  from copy import deepcopy
2
2
  from typing import Any
3
- from typing import Optional
4
3
 
5
4
  from fastapi import APIRouter
6
5
  from fastapi import Depends
@@ -123,19 +122,17 @@ async def get_workflow_tasks_statuses(
123
122
  .where(
124
123
  HistoryImageCache.latest_history_unit_id == HistoryUnit.id
125
124
  )
126
- .where(HistoryUnit.status == target_status.value)
125
+ .where(HistoryUnit.status == target_status)
127
126
  )
128
127
  res = await db.execute(stm)
129
128
  num_images = res.scalar()
130
- response[wftask.id][
131
- f"num_{target_status.value}_images"
132
- ] = num_images
129
+ response[wftask.id][f"num_{target_status}_images"] = num_images
133
130
 
134
131
  new_response = deepcopy(response)
135
132
  for key, value in response.items():
136
133
  if value is not None:
137
134
  num_total_images = sum(
138
- value[f"num_{target_status.value}_images"]
135
+ value[f"num_{target_status}_images"]
139
136
  for target_status in HistoryUnitStatus
140
137
  )
141
138
  if num_total_images > value["num_available_images"]:
@@ -213,7 +210,7 @@ async def get_history_run_units(
213
210
  dataset_id: int,
214
211
  workflowtask_id: int,
215
212
  history_run_id: int,
216
- unit_status: Optional[HistoryUnitStatus] = None,
213
+ unit_status: HistoryUnitStatus | None = None,
217
214
  user: UserOAuth = Depends(current_active_user),
218
215
  db: AsyncSession = Depends(get_async_db),
219
216
  pagination: PaginationRequest = Depends(get_pagination_params),
@@ -274,7 +271,7 @@ async def get_history_images(
274
271
  dataset_id: int,
275
272
  workflowtask_id: int,
276
273
  request_body: ImageQuery,
277
- unit_status: Optional[HistoryUnitStatusQuery] = None,
274
+ unit_status: HistoryUnitStatusQuery | None = None,
278
275
  user: UserOAuth = Depends(current_active_user),
279
276
  db: AsyncSession = Depends(get_async_db),
280
277
  pagination: PaginationRequest = Depends(get_pagination_params),
@@ -1,5 +1,4 @@
1
1
  from typing import Any
2
- from typing import Optional
3
2
 
4
3
  from fastapi import APIRouter
5
4
  from fastapi import Depends
@@ -44,7 +43,7 @@ class ImageQuery(BaseModel):
44
43
 
45
44
 
46
45
  class ImageQueryWithZarrUrl(ImageQuery):
47
- zarr_url: Optional[str] = None
46
+ zarr_url: str | None = None
48
47
 
49
48
 
50
49
  @router.post(
@@ -106,7 +105,7 @@ async def post_new_image(
106
105
  async def query_dataset_images(
107
106
  project_id: int,
108
107
  dataset_id: int,
109
- query: Optional[ImageQueryWithZarrUrl] = None,
108
+ query: ImageQueryWithZarrUrl | None = None,
110
109
  pagination: PaginationRequest = Depends(get_pagination_params),
111
110
  user: UserOAuth = Depends(current_active_user),
112
111
  db: AsyncSession = Depends(get_async_db),
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
+ from collections.abc import Iterator
2
3
  from pathlib import Path
3
- from typing import Iterator
4
- from typing import Optional
5
4
 
6
5
  from fastapi import APIRouter
7
6
  from fastapi import Depends
@@ -71,7 +70,7 @@ async def get_workflow_jobs(
71
70
  workflow_id: int,
72
71
  user: UserOAuth = Depends(current_active_user),
73
72
  db: AsyncSession = Depends(get_async_db),
74
- ) -> Optional[list[JobReadV2]]:
73
+ ) -> list[JobReadV2] | None:
75
74
  """
76
75
  Returns all the jobs related to a specific workflow
77
76
  """
@@ -123,7 +122,7 @@ async def read_job(
123
122
  show_tmp_logs: bool = False,
124
123
  user: UserOAuth = Depends(current_active_user),
125
124
  db: AsyncSession = Depends(get_async_db),
126
- ) -> Optional[JobReadV2]:
125
+ ) -> JobReadV2 | None:
127
126
  """
128
127
  Return info on an existing job
129
128
  """
@@ -139,7 +138,7 @@ async def read_job(
139
138
 
140
139
  if show_tmp_logs and (job.status == JobStatusTypeV2.SUBMITTED):
141
140
  try:
142
- with open(f"{job.working_dir}/{WORKFLOW_LOG_FILENAME}", "r") as f:
141
+ with open(f"{job.working_dir}/{WORKFLOW_LOG_FILENAME}") as f:
143
142
  job.log = f.read()
144
143
  except FileNotFoundError:
145
144
  pass
@@ -187,7 +186,7 @@ async def get_job_list(
187
186
  user: UserOAuth = Depends(current_active_user),
188
187
  log: bool = True,
189
188
  db: AsyncSession = Depends(get_async_db),
190
- ) -> Optional[list[JobReadV2]]:
189
+ ) -> list[JobReadV2] | None:
191
190
  """
192
191
  Get job list for given project
193
192
  """
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from fastapi import APIRouter
4
2
  from fastapi import Depends
5
3
  from fastapi import status
@@ -32,7 +30,7 @@ router = APIRouter()
32
30
  async def verify_unique_types(
33
31
  project_id: int,
34
32
  dataset_id: int,
35
- query: Optional[ImageQuery] = None,
33
+ query: ImageQuery | None = None,
36
34
  user: UserOAuth = Depends(current_active_user),
37
35
  db: AsyncSession = Depends(get_async_db),
38
36
  ) -> list[str]:
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from fastapi import APIRouter
4
2
  from fastapi import Depends
5
3
  from fastapi import HTTPException
@@ -50,7 +48,7 @@ async def create_project(
50
48
  project: ProjectCreateV2,
51
49
  user: UserOAuth = Depends(current_active_user),
52
50
  db: AsyncSession = Depends(get_async_db),
53
- ) -> Optional[ProjectReadV2]:
51
+ ) -> ProjectReadV2 | None:
54
52
  """
55
53
  Create new project
56
54
  """
@@ -76,7 +74,7 @@ async def read_project(
76
74
  project_id: int,
77
75
  user: UserOAuth = Depends(current_active_user),
78
76
  db: AsyncSession = Depends(get_async_db),
79
- ) -> Optional[ProjectReadV2]:
77
+ ) -> ProjectReadV2 | None:
80
78
  """
81
79
  Return info on an existing project
82
80
  """
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from fastapi import APIRouter
4
2
  from fastapi import Depends
5
3
  from fastapi import HTTPException
@@ -32,7 +30,7 @@ async def get_workflowtask_status(
32
30
  workflow_id: int,
33
31
  user: UserOAuth = Depends(current_active_user),
34
32
  db: AsyncSession = Depends(get_async_db),
35
- ) -> Optional[LegacyStatusReadV2]:
33
+ ) -> LegacyStatusReadV2 | None:
36
34
  """
37
35
  Extract the status of all `WorkflowTaskV2` of a given `WorkflowV2` that ran
38
36
  on a given `DatasetV2`.
@@ -119,7 +117,7 @@ async def get_workflowtask_status(
119
117
  except ValueError:
120
118
  logger.warning(
121
119
  f"Job {running_job.id} is submitted but its task list does not"
122
- f" contain a {WorkflowTaskStatusTypeV2.SUBMITTED.value} task."
120
+ f" contain a {WorkflowTaskStatusTypeV2.SUBMITTED} task."
123
121
  )
124
122
  first_submitted_index = 0
125
123
 
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  import os
3
3
  from pathlib import Path
4
- from typing import Optional
5
4
 
6
5
  from fastapi import APIRouter
7
6
  from fastapi import BackgroundTasks
@@ -57,7 +56,7 @@ async def apply_workflow(
57
56
  request: Request,
58
57
  user: UserOAuth = Depends(current_active_verified_user),
59
58
  db: AsyncSession = Depends(get_async_db),
60
- ) -> Optional[JobReadV2]:
59
+ ) -> JobReadV2 | None:
61
60
 
62
61
  # Remove non-submitted V2 jobs from the app state when the list grows
63
62
  # beyond a threshold
@@ -223,13 +222,13 @@ async def apply_workflow(
223
222
 
224
223
  # User appropriate FractalSSH object
225
224
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
226
- ssh_credentials = dict(
225
+ ssh_config = dict(
227
226
  user=user_settings.ssh_username,
228
227
  host=user_settings.ssh_host,
229
228
  key_path=user_settings.ssh_private_key_path,
230
229
  )
231
230
  fractal_ssh_list = request.app.state.fractal_ssh_list
232
- fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
231
+ fractal_ssh = fractal_ssh_list.get(**ssh_config)
233
232
  else:
234
233
  fractal_ssh = None
235
234
 
@@ -1,5 +1,4 @@
1
1
  from copy import deepcopy # noqa
2
- from typing import Optional
3
2
 
4
3
  from fastapi import APIRouter
5
4
  from fastapi import Depends
@@ -37,9 +36,9 @@ logger = set_logger(__name__)
37
36
  @router.get("/", response_model=list[TaskReadV2])
38
37
  async def get_list_task(
39
38
  args_schema: bool = True,
40
- category: Optional[str] = None,
41
- modality: Optional[str] = None,
42
- author: Optional[str] = None,
39
+ category: str | None = None,
40
+ modality: str | None = None,
41
+ author: str | None = None,
43
42
  user: UserOAuth = Depends(current_active_user),
44
43
  db: AsyncSession = Depends(get_async_db),
45
44
  ) -> list[TaskReadV2]:
@@ -98,7 +97,7 @@ async def patch_task(
98
97
  task_update: TaskUpdateV2,
99
98
  user: UserOAuth = Depends(current_active_verified_user),
100
99
  db: AsyncSession = Depends(get_async_db),
101
- ) -> Optional[TaskReadV2]:
100
+ ) -> TaskReadV2 | None:
102
101
  """
103
102
  Edit a specific task (restricted to task owner)
104
103
  """
@@ -135,11 +134,11 @@ async def patch_task(
135
134
  )
136
135
  async def create_task(
137
136
  task: TaskCreateV2,
138
- user_group_id: Optional[int] = None,
137
+ user_group_id: int | None = None,
139
138
  private: bool = False,
140
139
  user: UserOAuth = Depends(current_active_verified_user),
141
140
  db: AsyncSession = Depends(get_async_db),
142
- ) -> Optional[TaskReadV2]:
141
+ ) -> TaskReadV2 | None:
143
142
  """
144
143
  Create a new task
145
144
  """
@@ -1,6 +1,5 @@
1
1
  import json
2
2
  from pathlib import Path
3
- from typing import Optional
4
3
 
5
4
  from fastapi import APIRouter
6
5
  from fastapi import BackgroundTasks
@@ -41,6 +40,7 @@ from fractal_server.app.schemas.v2 import (
41
40
  TaskGroupActivityActionV2,
42
41
  )
43
42
  from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
43
+ from fractal_server.ssh._fabric import SSHConfig
44
44
  from fractal_server.tasks.v2.local.collect import (
45
45
  collect_local,
46
46
  )
@@ -65,7 +65,7 @@ class CollectionRequestData(BaseModel):
65
65
  """
66
66
 
67
67
  task_collect: TaskCollectPipV2
68
- file: Optional[UploadFile] = None
68
+ file: UploadFile | None = None
69
69
  origin: TaskGroupV2OriginEnum
70
70
 
71
71
  @model_validator(mode="before")
@@ -105,12 +105,12 @@ class CollectionRequestData(BaseModel):
105
105
 
106
106
 
107
107
  def parse_request_data(
108
- package: Optional[str] = Form(None),
109
- package_version: Optional[str] = Form(None),
110
- package_extras: Optional[str] = Form(None),
111
- python_version: Optional[str] = Form(None),
112
- pinned_package_versions: Optional[str] = Form(None),
113
- file: Optional[UploadFile] = File(None),
108
+ package: str | None = Form(None),
109
+ package_version: str | None = Form(None),
110
+ package_extras: str | None = Form(None),
111
+ python_version: str | None = Form(None),
112
+ pinned_package_versions: str | None = Form(None),
113
+ file: UploadFile | None = File(None),
114
114
  ) -> CollectionRequestData:
115
115
  """
116
116
  Expand the parsing/validation of `parse_form_data`, based on `file`.
@@ -156,7 +156,7 @@ async def collect_tasks_pip(
156
156
  background_tasks: BackgroundTasks,
157
157
  request_data: CollectionRequestData = Depends(parse_request_data),
158
158
  private: bool = False,
159
- user_group_id: Optional[int] = None,
159
+ user_group_id: int | None = None,
160
160
  user: UserOAuth = Depends(current_active_verified_user),
161
161
  db: AsyncSession = Depends(get_async_db),
162
162
  ) -> TaskGroupActivityV2Read:
@@ -342,19 +342,17 @@ async def collect_tasks_pip(
342
342
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
343
343
  # SSH task collection
344
344
  # Use appropriate FractalSSH object
345
- ssh_credentials = dict(
345
+ ssh_config = SSHConfig(
346
346
  user=user_settings.ssh_username,
347
347
  host=user_settings.ssh_host,
348
348
  key_path=user_settings.ssh_private_key_path,
349
349
  )
350
- fractal_ssh_list = request.app.state.fractal_ssh_list
351
- fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
352
350
 
353
351
  background_tasks.add_task(
354
352
  collect_ssh,
355
353
  task_group_id=task_group.id,
356
354
  task_group_activity_id=task_group_activity.id,
357
- fractal_ssh=fractal_ssh,
355
+ ssh_config=ssh_config,
358
356
  tasks_base_dir=user_settings.ssh_tasks_dir,
359
357
  wheel_file=wheel_file,
360
358
  )
@@ -2,7 +2,6 @@ import os
2
2
  import shlex
3
3
  import subprocess # nosec
4
4
  from pathlib import Path
5
- from typing import Optional
6
5
 
7
6
  from fastapi import APIRouter
8
7
  from fastapi import Depends
@@ -44,7 +43,7 @@ logger = set_logger(__name__)
44
43
  async def collect_task_custom(
45
44
  task_collect: TaskCollectCustomV2,
46
45
  private: bool = False,
47
- user_group_id: Optional[int] = None,
46
+ user_group_id: int | None = None,
48
47
  user: UserOAuth = Depends(current_active_verified_user),
49
48
  db: AsyncSession = Depends(get_async_db),
50
49
  ) -> list[TaskReadV2]:
@@ -152,6 +151,7 @@ async def collect_task_custom(
152
151
  pkg_name=task_collect.label,
153
152
  user_id=user.id,
154
153
  user_group_id=user_group_id,
154
+ version=task_collect.version,
155
155
  )
156
156
  TaskGroupCreateV2(**task_group_attrs)
157
157
 
@@ -159,13 +159,13 @@ async def collect_task_custom(
159
159
  await _verify_non_duplication_user_constraint(
160
160
  user_id=user.id,
161
161
  pkg_name=task_group_attrs["pkg_name"],
162
- version=None,
162
+ version=task_group_attrs["version"],
163
163
  db=db,
164
164
  )
165
165
  await _verify_non_duplication_group_constraint(
166
166
  user_group_id=task_group_attrs["user_group_id"],
167
167
  pkg_name=task_group_attrs["pkg_name"],
168
- version=None,
168
+ version=task_group_attrs["version"],
169
169
  db=db,
170
170
  )
171
171
 
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  from fastapi import APIRouter
4
2
  from fastapi import Depends
5
3
  from fastapi import HTTPException
@@ -37,12 +35,12 @@ logger = set_logger(__name__)
37
35
 
38
36
  @router.get("/activity/", response_model=list[TaskGroupActivityV2Read])
39
37
  async def get_task_group_activity_list(
40
- task_group_activity_id: Optional[int] = None,
41
- taskgroupv2_id: Optional[int] = None,
42
- pkg_name: Optional[str] = None,
43
- status: Optional[TaskGroupActivityStatusV2] = None,
44
- action: Optional[TaskGroupActivityActionV2] = None,
45
- timestamp_started_min: Optional[AwareDatetime] = None,
38
+ task_group_activity_id: int | None = None,
39
+ taskgroupv2_id: int | None = None,
40
+ pkg_name: str | None = None,
41
+ status: TaskGroupActivityStatusV2 | None = None,
42
+ action: TaskGroupActivityActionV2 | None = None,
43
+ timestamp_started_min: AwareDatetime | None = None,
46
44
  user: UserOAuth = Depends(current_active_user),
47
45
  db: AsyncSession = Depends(get_async_db),
48
46
  ) -> list[TaskGroupActivityV2Read]: