fractal-server 1.4.6__py3-none-any.whl → 2.0.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 (139) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/db/__init__.py +0 -1
  3. fractal_server/app/models/__init__.py +6 -8
  4. fractal_server/app/models/linkuserproject.py +9 -0
  5. fractal_server/app/models/security.py +6 -0
  6. fractal_server/app/models/v1/__init__.py +12 -0
  7. fractal_server/app/models/{dataset.py → v1/dataset.py} +5 -5
  8. fractal_server/app/models/{job.py → v1/job.py} +5 -5
  9. fractal_server/app/models/{project.py → v1/project.py} +5 -5
  10. fractal_server/app/models/{state.py → v1/state.py} +2 -2
  11. fractal_server/app/models/{task.py → v1/task.py} +7 -2
  12. fractal_server/app/models/{workflow.py → v1/workflow.py} +5 -5
  13. fractal_server/app/models/v2/__init__.py +22 -0
  14. fractal_server/app/models/v2/collection_state.py +21 -0
  15. fractal_server/app/models/v2/dataset.py +54 -0
  16. fractal_server/app/models/v2/job.py +51 -0
  17. fractal_server/app/models/v2/project.py +30 -0
  18. fractal_server/app/models/v2/task.py +93 -0
  19. fractal_server/app/models/v2/workflow.py +35 -0
  20. fractal_server/app/models/v2/workflowtask.py +49 -0
  21. fractal_server/app/routes/admin/__init__.py +0 -0
  22. fractal_server/app/routes/{admin.py → admin/v1.py} +42 -42
  23. fractal_server/app/routes/admin/v2.py +309 -0
  24. fractal_server/app/routes/api/v1/__init__.py +7 -7
  25. fractal_server/app/routes/api/v1/_aux_functions.py +8 -8
  26. fractal_server/app/routes/api/v1/dataset.py +48 -41
  27. fractal_server/app/routes/api/v1/job.py +14 -14
  28. fractal_server/app/routes/api/v1/project.py +30 -27
  29. fractal_server/app/routes/api/v1/task.py +26 -16
  30. fractal_server/app/routes/api/v1/task_collection.py +28 -16
  31. fractal_server/app/routes/api/v1/workflow.py +28 -28
  32. fractal_server/app/routes/api/v1/workflowtask.py +11 -11
  33. fractal_server/app/routes/api/v2/__init__.py +34 -0
  34. fractal_server/app/routes/api/v2/_aux_functions.py +502 -0
  35. fractal_server/app/routes/api/v2/dataset.py +293 -0
  36. fractal_server/app/routes/api/v2/images.py +279 -0
  37. fractal_server/app/routes/api/v2/job.py +200 -0
  38. fractal_server/app/routes/api/v2/project.py +186 -0
  39. fractal_server/app/routes/api/v2/status.py +150 -0
  40. fractal_server/app/routes/api/v2/submit.py +210 -0
  41. fractal_server/app/routes/api/v2/task.py +222 -0
  42. fractal_server/app/routes/api/v2/task_collection.py +239 -0
  43. fractal_server/app/routes/api/v2/task_legacy.py +59 -0
  44. fractal_server/app/routes/api/v2/workflow.py +380 -0
  45. fractal_server/app/routes/api/v2/workflowtask.py +265 -0
  46. fractal_server/app/routes/aux/_job.py +2 -2
  47. fractal_server/app/runner/__init__.py +0 -379
  48. fractal_server/app/runner/async_wrap.py +27 -0
  49. fractal_server/app/runner/components.py +5 -0
  50. fractal_server/app/runner/exceptions.py +129 -0
  51. fractal_server/app/runner/executors/__init__.py +0 -0
  52. fractal_server/app/runner/executors/slurm/__init__.py +3 -0
  53. fractal_server/app/runner/{_slurm → executors/slurm}/_batching.py +1 -1
  54. fractal_server/app/runner/executors/slurm/_check_jobs_status.py +72 -0
  55. fractal_server/app/runner/{_slurm → executors/slurm}/_executor_wait_thread.py +3 -4
  56. fractal_server/app/runner/{_slurm → executors/slurm}/_slurm_config.py +3 -152
  57. fractal_server/app/runner/{_slurm → executors/slurm}/_subprocess_run_as_user.py +42 -1
  58. fractal_server/app/runner/{_slurm → executors/slurm}/executor.py +46 -27
  59. fractal_server/app/runner/filenames.py +6 -0
  60. fractal_server/app/runner/set_start_and_last_task_index.py +39 -0
  61. fractal_server/app/runner/task_files.py +103 -0
  62. fractal_server/app/runner/v1/__init__.py +366 -0
  63. fractal_server/app/runner/{_common.py → v1/_common.py} +56 -111
  64. fractal_server/app/runner/{_local → v1/_local}/__init__.py +5 -4
  65. fractal_server/app/runner/{_local → v1/_local}/_local_config.py +6 -7
  66. fractal_server/app/runner/{_local → v1/_local}/_submit_setup.py +1 -5
  67. fractal_server/app/runner/v1/_slurm/__init__.py +312 -0
  68. fractal_server/app/runner/{_slurm → v1/_slurm}/_submit_setup.py +5 -11
  69. fractal_server/app/runner/v1/_slurm/get_slurm_config.py +163 -0
  70. fractal_server/app/runner/v1/common.py +117 -0
  71. fractal_server/app/runner/{handle_failed_job.py → v1/handle_failed_job.py} +8 -8
  72. fractal_server/app/runner/v2/__init__.py +336 -0
  73. fractal_server/app/runner/v2/_local/__init__.py +162 -0
  74. fractal_server/app/runner/v2/_local/_local_config.py +118 -0
  75. fractal_server/app/runner/v2/_local/_submit_setup.py +52 -0
  76. fractal_server/app/runner/v2/_local/executor.py +100 -0
  77. fractal_server/app/runner/{_slurm → v2/_slurm}/__init__.py +38 -47
  78. fractal_server/app/runner/v2/_slurm/_submit_setup.py +82 -0
  79. fractal_server/app/runner/v2/_slurm/get_slurm_config.py +182 -0
  80. fractal_server/app/runner/v2/deduplicate_list.py +23 -0
  81. fractal_server/app/runner/v2/handle_failed_job.py +165 -0
  82. fractal_server/app/runner/v2/merge_outputs.py +38 -0
  83. fractal_server/app/runner/v2/runner.py +343 -0
  84. fractal_server/app/runner/v2/runner_functions.py +374 -0
  85. fractal_server/app/runner/v2/runner_functions_low_level.py +130 -0
  86. fractal_server/app/runner/v2/task_interface.py +62 -0
  87. fractal_server/app/runner/v2/v1_compat.py +31 -0
  88. fractal_server/app/schemas/__init__.py +1 -42
  89. fractal_server/app/schemas/_validators.py +28 -5
  90. fractal_server/app/schemas/v1/__init__.py +36 -0
  91. fractal_server/app/schemas/{applyworkflow.py → v1/applyworkflow.py} +18 -18
  92. fractal_server/app/schemas/{dataset.py → v1/dataset.py} +30 -30
  93. fractal_server/app/schemas/{dumps.py → v1/dumps.py} +8 -8
  94. fractal_server/app/schemas/{manifest.py → v1/manifest.py} +5 -5
  95. fractal_server/app/schemas/{project.py → v1/project.py} +9 -9
  96. fractal_server/app/schemas/{task.py → v1/task.py} +12 -12
  97. fractal_server/app/schemas/{task_collection.py → v1/task_collection.py} +7 -7
  98. fractal_server/app/schemas/{workflow.py → v1/workflow.py} +38 -38
  99. fractal_server/app/schemas/v2/__init__.py +37 -0
  100. fractal_server/app/schemas/v2/dataset.py +126 -0
  101. fractal_server/app/schemas/v2/dumps.py +87 -0
  102. fractal_server/app/schemas/v2/job.py +114 -0
  103. fractal_server/app/schemas/v2/manifest.py +159 -0
  104. fractal_server/app/schemas/v2/project.py +34 -0
  105. fractal_server/app/schemas/v2/status.py +16 -0
  106. fractal_server/app/schemas/v2/task.py +151 -0
  107. fractal_server/app/schemas/v2/task_collection.py +109 -0
  108. fractal_server/app/schemas/v2/workflow.py +79 -0
  109. fractal_server/app/schemas/v2/workflowtask.py +208 -0
  110. fractal_server/config.py +13 -10
  111. fractal_server/images/__init__.py +4 -0
  112. fractal_server/images/models.py +136 -0
  113. fractal_server/images/tools.py +84 -0
  114. fractal_server/main.py +11 -3
  115. fractal_server/migrations/env.py +0 -2
  116. fractal_server/migrations/versions/5bf02391cfef_v2.py +245 -0
  117. fractal_server/tasks/__init__.py +0 -5
  118. fractal_server/tasks/endpoint_operations.py +13 -19
  119. fractal_server/tasks/utils.py +35 -0
  120. fractal_server/tasks/{_TaskCollectPip.py → v1/_TaskCollectPip.py} +3 -3
  121. fractal_server/tasks/v1/__init__.py +0 -0
  122. fractal_server/tasks/{background_operations.py → v1/background_operations.py} +20 -52
  123. fractal_server/tasks/v1/get_collection_data.py +14 -0
  124. fractal_server/tasks/v2/_TaskCollectPip.py +103 -0
  125. fractal_server/tasks/v2/__init__.py +0 -0
  126. fractal_server/tasks/v2/background_operations.py +381 -0
  127. fractal_server/tasks/v2/get_collection_data.py +14 -0
  128. fractal_server/urls.py +13 -0
  129. {fractal_server-1.4.6.dist-info → fractal_server-2.0.0.dist-info}/METADATA +11 -12
  130. fractal_server-2.0.0.dist-info/RECORD +169 -0
  131. fractal_server/app/runner/_slurm/.gitignore +0 -2
  132. fractal_server/app/runner/common.py +0 -307
  133. fractal_server/app/schemas/json_schemas/manifest.json +0 -81
  134. fractal_server-1.4.6.dist-info/RECORD +0 -97
  135. /fractal_server/app/runner/{_slurm → executors/slurm}/remote.py +0 -0
  136. /fractal_server/app/runner/{_local → v1/_local}/executor.py +0 -0
  137. {fractal_server-1.4.6.dist-info → fractal_server-2.0.0.dist-info}/LICENSE +0 -0
  138. {fractal_server-1.4.6.dist-info → fractal_server-2.0.0.dist-info}/WHEEL +0 -0
  139. {fractal_server-1.4.6.dist-info → fractal_server-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import json
2
+ from json.decoder import JSONDecodeError
2
3
  from pathlib import Path
3
4
  from typing import Optional
4
5
 
@@ -12,20 +13,20 @@ from sqlmodel import select
12
13
 
13
14
  from ....db import AsyncSession
14
15
  from ....db import get_async_db
15
- from ....models import ApplyWorkflow
16
- from ....models import Dataset
17
- from ....models import Project
18
- from ....models import Resource
19
- from ....runner._common import HISTORY_FILENAME
20
- from ....schemas import DatasetCreate
21
- from ....schemas import DatasetRead
22
- from ....schemas import DatasetStatusRead
23
- from ....schemas import DatasetUpdate
24
- from ....schemas import ResourceCreate
25
- from ....schemas import ResourceRead
26
- from ....schemas import ResourceUpdate
27
- from ....schemas import WorkflowExport
28
- from ....schemas import WorkflowTaskExport
16
+ from ....models.v1 import ApplyWorkflow
17
+ from ....models.v1 import Dataset
18
+ from ....models.v1 import Project
19
+ from ....models.v1 import Resource
20
+ from ....runner.filenames import HISTORY_FILENAME
21
+ from ....schemas.v1 import DatasetCreateV1
22
+ from ....schemas.v1 import DatasetReadV1
23
+ from ....schemas.v1 import DatasetStatusReadV1
24
+ from ....schemas.v1 import DatasetUpdateV1
25
+ from ....schemas.v1 import ResourceCreateV1
26
+ from ....schemas.v1 import ResourceReadV1
27
+ from ....schemas.v1 import ResourceUpdateV1
28
+ from ....schemas.v1 import WorkflowExportV1
29
+ from ....schemas.v1 import WorkflowTaskExportV1
29
30
  from ....security import current_active_user
30
31
  from ....security import User
31
32
  from ._aux_functions import _get_dataset_check_owner
@@ -39,15 +40,15 @@ router = APIRouter()
39
40
 
40
41
  @router.post(
41
42
  "/project/{project_id}/dataset/",
42
- response_model=DatasetRead,
43
+ response_model=DatasetReadV1,
43
44
  status_code=status.HTTP_201_CREATED,
44
45
  )
45
46
  async def create_dataset(
46
47
  project_id: int,
47
- dataset: DatasetCreate,
48
+ dataset: DatasetCreateV1,
48
49
  user: User = Depends(current_active_user),
49
50
  db: AsyncSession = Depends(get_async_db),
50
- ) -> Optional[DatasetRead]:
51
+ ) -> Optional[DatasetReadV1]:
51
52
  """
52
53
  Add new dataset to current project
53
54
  """
@@ -65,14 +66,14 @@ async def create_dataset(
65
66
 
66
67
  @router.get(
67
68
  "/project/{project_id}/dataset/",
68
- response_model=list[DatasetRead],
69
+ response_model=list[DatasetReadV1],
69
70
  )
70
71
  async def read_dataset_list(
71
72
  project_id: int,
72
73
  history: bool = True,
73
74
  user: User = Depends(current_active_user),
74
75
  db: AsyncSession = Depends(get_async_db),
75
- ) -> Optional[list[DatasetRead]]:
76
+ ) -> Optional[list[DatasetReadV1]]:
76
77
  """
77
78
  Get dataset list for given project
78
79
  """
@@ -96,14 +97,14 @@ async def read_dataset_list(
96
97
 
97
98
  @router.get(
98
99
  "/project/{project_id}/dataset/{dataset_id}/",
99
- response_model=DatasetRead,
100
+ response_model=DatasetReadV1,
100
101
  )
101
102
  async def read_dataset(
102
103
  project_id: int,
103
104
  dataset_id: int,
104
105
  user: User = Depends(current_active_user),
105
106
  db: AsyncSession = Depends(get_async_db),
106
- ) -> Optional[DatasetRead]:
107
+ ) -> Optional[DatasetReadV1]:
107
108
  """
108
109
  Get info on a dataset associated to the current project
109
110
  """
@@ -120,15 +121,15 @@ async def read_dataset(
120
121
 
121
122
  @router.patch(
122
123
  "/project/{project_id}/dataset/{dataset_id}/",
123
- response_model=DatasetRead,
124
+ response_model=DatasetReadV1,
124
125
  )
125
126
  async def update_dataset(
126
127
  project_id: int,
127
128
  dataset_id: int,
128
- dataset_update: DatasetUpdate,
129
+ dataset_update: DatasetUpdateV1,
129
130
  user: User = Depends(current_active_user),
130
131
  db: AsyncSession = Depends(get_async_db),
131
- ) -> Optional[DatasetRead]:
132
+ ) -> Optional[DatasetReadV1]:
132
133
  """
133
134
  Edit a dataset associated to the current project
134
135
  """
@@ -229,16 +230,16 @@ async def delete_dataset(
229
230
 
230
231
  @router.post(
231
232
  "/project/{project_id}/dataset/{dataset_id}/resource/",
232
- response_model=ResourceRead,
233
+ response_model=ResourceReadV1,
233
234
  status_code=status.HTTP_201_CREATED,
234
235
  )
235
236
  async def create_resource(
236
237
  project_id: int,
237
238
  dataset_id: int,
238
- resource: ResourceCreate,
239
+ resource: ResourceCreateV1,
239
240
  user: User = Depends(current_active_user),
240
241
  db: AsyncSession = Depends(get_async_db),
241
- ) -> Optional[ResourceRead]:
242
+ ) -> Optional[ResourceReadV1]:
242
243
  """
243
244
  Add resource to an existing dataset
244
245
  """
@@ -259,14 +260,14 @@ async def create_resource(
259
260
 
260
261
  @router.get(
261
262
  "/project/{project_id}/dataset/{dataset_id}/resource/",
262
- response_model=list[ResourceRead],
263
+ response_model=list[ResourceReadV1],
263
264
  )
264
265
  async def get_resource_list(
265
266
  project_id: int,
266
267
  dataset_id: int,
267
268
  user: User = Depends(current_active_user),
268
269
  db: AsyncSession = Depends(get_async_db),
269
- ) -> Optional[list[ResourceRead]]:
270
+ ) -> Optional[list[ResourceReadV1]]:
270
271
  """
271
272
  Get resources from a dataset
272
273
  """
@@ -285,16 +286,16 @@ async def get_resource_list(
285
286
 
286
287
  @router.patch(
287
288
  "/project/{project_id}/dataset/{dataset_id}/resource/{resource_id}/",
288
- response_model=ResourceRead,
289
+ response_model=ResourceReadV1,
289
290
  )
290
291
  async def update_resource(
291
292
  project_id: int,
292
293
  dataset_id: int,
293
294
  resource_id: int,
294
- resource_update: ResourceUpdate,
295
+ resource_update: ResourceUpdateV1,
295
296
  user: User = Depends(current_active_user),
296
297
  db: AsyncSession = Depends(get_async_db),
297
- ) -> Optional[ResourceRead]:
298
+ ) -> Optional[ResourceReadV1]:
298
299
  """
299
300
  Edit a resource of a dataset
300
301
  """
@@ -360,14 +361,14 @@ async def delete_resource(
360
361
 
361
362
  @router.get(
362
363
  "/project/{project_id}/dataset/{dataset_id}/export_history/",
363
- response_model=WorkflowExport,
364
+ response_model=WorkflowExportV1,
364
365
  )
365
366
  async def export_history_as_workflow(
366
367
  project_id: int,
367
368
  dataset_id: int,
368
369
  user: User = Depends(current_active_user),
369
370
  db: AsyncSession = Depends(get_async_db),
370
- ) -> Optional[WorkflowExport]:
371
+ ) -> Optional[WorkflowExportV1]:
371
372
  """
372
373
  Extract a reproducible workflow from the dataset history.
373
374
  """
@@ -412,7 +413,7 @@ async def export_history_as_workflow(
412
413
  wftask = history_item["workflowtask"]
413
414
  wftask_status = history_item["status"]
414
415
  if wftask_status == "done":
415
- task_list.append(WorkflowTaskExport(**wftask))
416
+ task_list.append(WorkflowTaskExportV1(**wftask))
416
417
 
417
418
  def _slugify_dataset_name(_name: str) -> str:
418
419
  _new_name = _name
@@ -422,20 +423,20 @@ async def export_history_as_workflow(
422
423
 
423
424
  name = f"history_{_slugify_dataset_name(dataset.name)}"
424
425
 
425
- workflow = WorkflowExport(name=name, task_list=task_list)
426
+ workflow = WorkflowExportV1(name=name, task_list=task_list)
426
427
  return workflow
427
428
 
428
429
 
429
430
  @router.get(
430
431
  "/project/{project_id}/dataset/{dataset_id}/status/",
431
- response_model=DatasetStatusRead,
432
+ response_model=DatasetStatusReadV1,
432
433
  )
433
434
  async def get_workflowtask_status(
434
435
  project_id: int,
435
436
  dataset_id: int,
436
437
  user: User = Depends(current_active_user),
437
438
  db: AsyncSession = Depends(get_async_db),
438
- ) -> Optional[DatasetStatusRead]:
439
+ ) -> Optional[DatasetStatusReadV1]:
439
440
  """
440
441
  Extract the status of all `WorkflowTask`s that ran on a given `Dataset`.
441
442
  """
@@ -511,21 +512,27 @@ async def get_workflowtask_status(
511
512
  history = json.load(f)
512
513
  except FileNotFoundError:
513
514
  history = []
515
+ except JSONDecodeError:
516
+ raise HTTPException(
517
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
518
+ detail="History file does not include a valid JSON.",
519
+ )
520
+
514
521
  for history_item in history:
515
522
  wftask_id = history_item["workflowtask"]["id"]
516
523
  wftask_status = history_item["status"]
517
524
  workflow_tasks_status_dict[wftask_id] = wftask_status
518
525
 
519
- response_body = DatasetStatusRead(status=workflow_tasks_status_dict)
526
+ response_body = DatasetStatusReadV1(status=workflow_tasks_status_dict)
520
527
  return response_body
521
528
 
522
529
 
523
- @router.get("/dataset/", response_model=list[DatasetRead])
530
+ @router.get("/dataset/", response_model=list[DatasetReadV1])
524
531
  async def get_user_datasets(
525
532
  history: bool = True,
526
533
  user: User = Depends(current_active_user),
527
534
  db: AsyncSession = Depends(get_async_db),
528
- ) -> list[DatasetRead]:
535
+ ) -> list[DatasetReadV1]:
529
536
  """
530
537
  Returns all the datasets of the current user
531
538
  """
@@ -10,11 +10,11 @@ from sqlmodel import select
10
10
 
11
11
  from ....db import AsyncSession
12
12
  from ....db import get_async_db
13
- from ....models import ApplyWorkflow
14
- from ....models import JobStatusType
15
- from ....models import Project
16
- from ....runner._common import WORKFLOW_LOG_FILENAME
17
- from ....schemas import ApplyWorkflowRead
13
+ from ....models.v1 import ApplyWorkflow
14
+ from ....models.v1 import JobStatusTypeV1
15
+ from ....models.v1 import Project
16
+ from ....runner.filenames import WORKFLOW_LOG_FILENAME
17
+ from ....schemas.v1 import ApplyWorkflowReadV1
18
18
  from ....security import current_active_user
19
19
  from ....security import User
20
20
  from ...aux._job import _write_shutdown_file
@@ -27,12 +27,12 @@ from ._aux_functions import _get_workflow_check_owner
27
27
  router = APIRouter()
28
28
 
29
29
 
30
- @router.get("/job/", response_model=list[ApplyWorkflowRead])
30
+ @router.get("/job/", response_model=list[ApplyWorkflowReadV1])
31
31
  async def get_user_jobs(
32
32
  user: User = Depends(current_active_user),
33
33
  log: bool = True,
34
34
  db: AsyncSession = Depends(get_async_db),
35
- ) -> list[ApplyWorkflowRead]:
35
+ ) -> list[ApplyWorkflowReadV1]:
36
36
  """
37
37
  Returns all the jobs of the current user
38
38
  """
@@ -50,14 +50,14 @@ async def get_user_jobs(
50
50
 
51
51
  @router.get(
52
52
  "/project/{project_id}/workflow/{workflow_id}/job/",
53
- response_model=list[ApplyWorkflowRead],
53
+ response_model=list[ApplyWorkflowReadV1],
54
54
  )
55
55
  async def get_workflow_jobs(
56
56
  project_id: int,
57
57
  workflow_id: int,
58
58
  user: User = Depends(current_active_user),
59
59
  db: AsyncSession = Depends(get_async_db),
60
- ) -> Optional[list[ApplyWorkflowRead]]:
60
+ ) -> Optional[list[ApplyWorkflowReadV1]]:
61
61
  """
62
62
  Returns all the jobs related to a specific workflow
63
63
  """
@@ -72,7 +72,7 @@ async def get_workflow_jobs(
72
72
 
73
73
  @router.get(
74
74
  "/project/{project_id}/job/{job_id}/",
75
- response_model=ApplyWorkflowRead,
75
+ response_model=ApplyWorkflowReadV1,
76
76
  )
77
77
  async def read_job(
78
78
  project_id: int,
@@ -80,7 +80,7 @@ async def read_job(
80
80
  show_tmp_logs: bool = False,
81
81
  user: User = Depends(current_active_user),
82
82
  db: AsyncSession = Depends(get_async_db),
83
- ) -> Optional[ApplyWorkflowRead]:
83
+ ) -> Optional[ApplyWorkflowReadV1]:
84
84
  """
85
85
  Return info on an existing job
86
86
  """
@@ -94,7 +94,7 @@ async def read_job(
94
94
  job = output["job"]
95
95
  await db.close()
96
96
 
97
- if show_tmp_logs and (job.status == JobStatusType.SUBMITTED):
97
+ if show_tmp_logs and (job.status == JobStatusTypeV1.SUBMITTED):
98
98
  try:
99
99
  with open(f"{job.working_dir}/{WORKFLOW_LOG_FILENAME}", "r") as f:
100
100
  job.log = f.read()
@@ -140,14 +140,14 @@ async def download_job_logs(
140
140
 
141
141
  @router.get(
142
142
  "/project/{project_id}/job/",
143
- response_model=list[ApplyWorkflowRead],
143
+ response_model=list[ApplyWorkflowReadV1],
144
144
  )
145
145
  async def get_job_list(
146
146
  project_id: int,
147
147
  user: User = Depends(current_active_user),
148
148
  log: bool = True,
149
149
  db: AsyncSession = Depends(get_async_db),
150
- ) -> Optional[list[ApplyWorkflowRead]]:
150
+ ) -> Optional[list[ApplyWorkflowReadV1]]:
151
151
  """
152
152
  Get job list for given project
153
153
  """
@@ -18,20 +18,22 @@ from .....logger import set_logger
18
18
  from .....syringe import Inject
19
19
  from ....db import AsyncSession
20
20
  from ....db import get_async_db
21
- from ....models import ApplyWorkflow
22
- from ....models import Dataset
23
21
  from ....models import LinkUserProject
24
- from ....models import Project
25
- from ....models import Workflow
26
- from ....runner import submit_workflow
27
- from ....runner import validate_workflow_compatibility
28
- from ....runner.common import set_start_and_last_task_index
29
- from ....schemas import ApplyWorkflowCreate
30
- from ....schemas import ApplyWorkflowRead
31
- from ....schemas import JobStatusType
32
- from ....schemas import ProjectCreate
33
- from ....schemas import ProjectRead
34
- from ....schemas import ProjectUpdate
22
+ from ....models.v1 import ApplyWorkflow
23
+ from ....models.v1 import Dataset
24
+ from ....models.v1 import Project
25
+ from ....models.v1 import Workflow
26
+ from ....runner.set_start_and_last_task_index import (
27
+ set_start_and_last_task_index,
28
+ )
29
+ from ....runner.v1 import submit_workflow
30
+ from ....runner.v1 import validate_workflow_compatibility
31
+ from ....schemas.v1 import ApplyWorkflowCreateV1
32
+ from ....schemas.v1 import ApplyWorkflowReadV1
33
+ from ....schemas.v1 import JobStatusTypeV1
34
+ from ....schemas.v1 import ProjectCreateV1
35
+ from ....schemas.v1 import ProjectReadV1
36
+ from ....schemas.v1 import ProjectUpdateV1
35
37
  from ....security import current_active_user
36
38
  from ....security import current_active_verified_user
37
39
  from ....security import User
@@ -48,7 +50,7 @@ def _encode_as_utc(dt: datetime):
48
50
  return dt.replace(tzinfo=timezone.utc).isoformat()
49
51
 
50
52
 
51
- @router.get("/", response_model=list[ProjectRead])
53
+ @router.get("/", response_model=list[ProjectReadV1])
52
54
  async def get_list_project(
53
55
  user: User = Depends(current_active_user),
54
56
  db: AsyncSession = Depends(get_async_db),
@@ -67,12 +69,12 @@ async def get_list_project(
67
69
  return project_list
68
70
 
69
71
 
70
- @router.post("/", response_model=ProjectRead, status_code=201)
72
+ @router.post("/", response_model=ProjectReadV1, status_code=201)
71
73
  async def create_project(
72
- project: ProjectCreate,
74
+ project: ProjectCreateV1,
73
75
  user: User = Depends(current_active_user),
74
76
  db: AsyncSession = Depends(get_async_db),
75
- ) -> Optional[ProjectRead]:
77
+ ) -> Optional[ProjectReadV1]:
76
78
  """
77
79
  Create new poject
78
80
  """
@@ -102,12 +104,12 @@ async def create_project(
102
104
  return db_project
103
105
 
104
106
 
105
- @router.get("/{project_id}/", response_model=ProjectRead)
107
+ @router.get("/{project_id}/", response_model=ProjectReadV1)
106
108
  async def read_project(
107
109
  project_id: int,
108
110
  user: User = Depends(current_active_user),
109
111
  db: AsyncSession = Depends(get_async_db),
110
- ) -> Optional[ProjectRead]:
112
+ ) -> Optional[ProjectReadV1]:
111
113
  """
112
114
  Return info on an existing project
113
115
  """
@@ -118,10 +120,10 @@ async def read_project(
118
120
  return project
119
121
 
120
122
 
121
- @router.patch("/{project_id}/", response_model=ProjectRead)
123
+ @router.patch("/{project_id}/", response_model=ProjectReadV1)
122
124
  async def update_project(
123
125
  project_id: int,
124
- project_update: ProjectUpdate,
126
+ project_update: ProjectUpdateV1,
125
127
  user: User = Depends(current_active_user),
126
128
  db: AsyncSession = Depends(get_async_db),
127
129
  ):
@@ -241,18 +243,18 @@ async def delete_project(
241
243
  @router.post(
242
244
  "/{project_id}/workflow/{workflow_id}/apply/",
243
245
  status_code=status.HTTP_202_ACCEPTED,
244
- response_model=ApplyWorkflowRead,
246
+ response_model=ApplyWorkflowReadV1,
245
247
  )
246
248
  async def apply_workflow(
247
249
  project_id: int,
248
250
  workflow_id: int,
249
- apply_workflow: ApplyWorkflowCreate,
251
+ apply_workflow: ApplyWorkflowCreateV1,
250
252
  background_tasks: BackgroundTasks,
251
253
  input_dataset_id: int,
252
254
  output_dataset_id: int,
253
255
  user: User = Depends(current_active_verified_user),
254
256
  db: AsyncSession = Depends(get_async_db),
255
- ) -> Optional[ApplyWorkflowRead]:
257
+ ) -> Optional[ApplyWorkflowReadV1]:
256
258
 
257
259
  output = await _get_dataset_check_owner(
258
260
  project_id=project_id,
@@ -362,7 +364,7 @@ async def apply_workflow(
362
364
  stm = (
363
365
  select(ApplyWorkflow)
364
366
  .where(ApplyWorkflow.output_dataset_id == output_dataset_id)
365
- .where(ApplyWorkflow.status == JobStatusType.SUBMITTED)
367
+ .where(ApplyWorkflow.status == JobStatusTypeV1.SUBMITTED)
366
368
  )
367
369
  res = await db.execute(stm)
368
370
  if res.scalars().all():
@@ -449,8 +451,9 @@ async def apply_workflow(
449
451
  raise HTTPException(
450
452
  status_code=status.HTTP_429_TOO_MANY_REQUESTS,
451
453
  detail=(
452
- f"The endpoint 'POST /{project_id}/workflow/{workflow_id}/"
453
- "apply/' was called several times with an interval of less "
454
+ f"The endpoint 'POST /api/v1/project/{project_id}/workflow/"
455
+ f"{workflow_id}/apply/' "
456
+ "was called several times within an interval of less "
454
457
  f"than {settings.FRACTAL_API_SUBMIT_RATE_LIMIT} seconds, using"
455
458
  " the same foreign keys. If it was intentional, please wait "
456
459
  "and try again."
@@ -11,11 +11,12 @@ from sqlmodel import select
11
11
  from .....logger import set_logger
12
12
  from ....db import AsyncSession
13
13
  from ....db import get_async_db
14
- from ....models import Task
15
- from ....models import WorkflowTask
16
- from ....schemas import TaskCreate
17
- from ....schemas import TaskRead
18
- from ....schemas import TaskUpdate
14
+ from ....models.v1 import Task
15
+ from ....models.v1 import WorkflowTask
16
+ from ....models.v2 import TaskV2
17
+ from ....schemas.v1 import TaskCreateV1
18
+ from ....schemas.v1 import TaskReadV1
19
+ from ....schemas.v1 import TaskUpdateV1
19
20
  from ....security import current_active_user
20
21
  from ....security import current_active_verified_user
21
22
  from ....security import User
@@ -26,12 +27,12 @@ router = APIRouter()
26
27
  logger = set_logger(__name__)
27
28
 
28
29
 
29
- @router.get("/", response_model=list[TaskRead])
30
+ @router.get("/", response_model=list[TaskReadV1])
30
31
  async def get_list_task(
31
32
  user: User = Depends(current_active_user),
32
33
  args_schema: bool = True,
33
34
  db: AsyncSession = Depends(get_async_db),
34
- ) -> list[TaskRead]:
35
+ ) -> list[TaskReadV1]:
35
36
  """
36
37
  Get list of available tasks
37
38
  """
@@ -46,12 +47,12 @@ async def get_list_task(
46
47
  return task_list
47
48
 
48
49
 
49
- @router.get("/{task_id}/", response_model=TaskRead)
50
+ @router.get("/{task_id}/", response_model=TaskReadV1)
50
51
  async def get_task(
51
52
  task_id: int,
52
53
  user: User = Depends(current_active_user),
53
54
  db: AsyncSession = Depends(get_async_db),
54
- ) -> TaskRead:
55
+ ) -> TaskReadV1:
55
56
  """
56
57
  Get info on a specific task
57
58
  """
@@ -64,13 +65,13 @@ async def get_task(
64
65
  return task
65
66
 
66
67
 
67
- @router.patch("/{task_id}/", response_model=TaskRead)
68
+ @router.patch("/{task_id}/", response_model=TaskReadV1)
68
69
  async def patch_task(
69
70
  task_id: int,
70
- task_update: TaskUpdate,
71
+ task_update: TaskUpdateV1,
71
72
  user: User = Depends(current_active_verified_user),
72
73
  db: AsyncSession = Depends(get_async_db),
73
- ) -> Optional[TaskRead]:
74
+ ) -> Optional[TaskReadV1]:
74
75
  """
75
76
  Edit a specific task (restricted to superusers and task owner)
76
77
  """
@@ -109,12 +110,14 @@ async def patch_task(
109
110
  return db_task
110
111
 
111
112
 
112
- @router.post("/", response_model=TaskRead, status_code=status.HTTP_201_CREATED)
113
+ @router.post(
114
+ "/", response_model=TaskReadV1, status_code=status.HTTP_201_CREATED
115
+ )
113
116
  async def create_task(
114
- task: TaskCreate,
117
+ task: TaskCreateV1,
115
118
  user: User = Depends(current_active_verified_user),
116
119
  db: AsyncSession = Depends(get_async_db),
117
- ) -> Optional[TaskRead]:
120
+ ) -> Optional[TaskReadV1]:
118
121
  """
119
122
  Create a new task
120
123
  """
@@ -143,7 +146,14 @@ async def create_task(
143
146
  if res.scalars().all():
144
147
  raise HTTPException(
145
148
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
146
- detail=f'Task source "{task.source}" already in use',
149
+ detail=f"Source '{task.source}' already used by some TaskV1",
150
+ )
151
+ stm = select(TaskV2).where(TaskV2.source == task.source)
152
+ res = await db.execute(stm)
153
+ if res.scalars().all():
154
+ raise HTTPException(
155
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
156
+ detail=f"Source '{task.source}' already used by some TaskV2",
147
157
  )
148
158
 
149
159
  # Add task
@@ -15,24 +15,26 @@ from .....config import get_settings
15
15
  from .....logger import close_logger
16
16
  from .....logger import set_logger
17
17
  from .....syringe import Inject
18
- from .....tasks._TaskCollectPip import _TaskCollectPip
19
- from .....tasks.background_operations import background_collect_pip
20
- from .....tasks.endpoint_operations import create_package_dir_pip
21
- from .....tasks.endpoint_operations import download_package
22
- from .....tasks.endpoint_operations import get_collection_data
23
- from .....tasks.endpoint_operations import inspect_package
24
- from .....tasks.utils import get_collection_log
25
- from .....tasks.utils import slugify_task_name
26
18
  from ....db import AsyncSession
27
19
  from ....db import get_async_db
28
- from ....models import State
29
- from ....models import Task
30
- from ....schemas import StateRead
31
- from ....schemas import TaskCollectPip
32
- from ....schemas import TaskCollectStatus
20
+ from ....models.v1 import State
21
+ from ....models.v1 import Task
22
+ from ....schemas.state import StateRead
23
+ from ....schemas.v1 import TaskCollectPipV1
24
+ from ....schemas.v1 import TaskCollectStatusV1
33
25
  from ....security import current_active_user
34
26
  from ....security import current_active_verified_user
35
27
  from ....security import User
28
+ from fractal_server.tasks.endpoint_operations import create_package_dir_pip
29
+ from fractal_server.tasks.endpoint_operations import download_package
30
+ from fractal_server.tasks.endpoint_operations import inspect_package
31
+ from fractal_server.tasks.utils import get_collection_log
32
+ from fractal_server.tasks.utils import slugify_task_name
33
+ from fractal_server.tasks.v1._TaskCollectPip import _TaskCollectPip
34
+ from fractal_server.tasks.v1.background_operations import (
35
+ background_collect_pip,
36
+ )
37
+ from fractal_server.tasks.v1.get_collection_data import get_collection_data
36
38
 
37
39
  router = APIRouter()
38
40
 
@@ -57,7 +59,7 @@ logger = set_logger(__name__)
57
59
  },
58
60
  )
59
61
  async def collect_tasks_pip(
60
- task_collect: TaskCollectPip,
62
+ task_collect: TaskCollectPipV1,
61
63
  background_tasks: BackgroundTasks,
62
64
  response: Response,
63
65
  user: User = Depends(current_active_verified_user),
@@ -138,6 +140,16 @@ async def collect_tasks_pip(
138
140
  f"Original error: {e}"
139
141
  ),
140
142
  )
143
+ except ValidationError as e:
144
+ await db.close()
145
+ raise HTTPException(
146
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
147
+ detail=(
148
+ "Cannot collect package. Possible reason: an old version "
149
+ "of the same package has already been collected. "
150
+ f"Original error: {e}"
151
+ ),
152
+ )
141
153
  task_collect_status.info = "Already installed"
142
154
  state = State(data=task_collect_status.sanitised_dict())
143
155
  response.status_code == status.HTTP_200_OK
@@ -162,7 +174,7 @@ async def collect_tasks_pip(
162
174
 
163
175
  # All checks are OK, proceed with task collection
164
176
  full_venv_path = venv_path.relative_to(settings.FRACTAL_TASKS_DIR)
165
- collection_status = TaskCollectStatus(
177
+ collection_status = TaskCollectStatusV1(
166
178
  status="pending", venv_path=full_venv_path, package=task_pkg.package
167
179
  )
168
180
 
@@ -214,7 +226,7 @@ async def check_collection_status(
214
226
  status_code=status.HTTP_404_NOT_FOUND,
215
227
  detail=f"No task collection info with id={state_id}",
216
228
  )
217
- data = TaskCollectStatus(**state.data)
229
+ data = TaskCollectStatusV1(**state.data)
218
230
 
219
231
  # In some cases (i.e. a successful or ongoing task collection), data.log is
220
232
  # not set; if so, we collect the current logs