fractal-server 2.13.0__py3-none-any.whl → 2.14.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.
- fractal_server/__init__.py +1 -1
- fractal_server/__main__.py +3 -1
- fractal_server/app/models/linkusergroup.py +6 -2
- fractal_server/app/models/v2/__init__.py +11 -1
- fractal_server/app/models/v2/accounting.py +35 -0
- fractal_server/app/models/v2/dataset.py +1 -11
- fractal_server/app/models/v2/history.py +78 -0
- fractal_server/app/models/v2/job.py +10 -3
- fractal_server/app/models/v2/task_group.py +2 -2
- fractal_server/app/models/v2/workflow.py +1 -1
- fractal_server/app/models/v2/workflowtask.py +1 -1
- fractal_server/app/routes/admin/v2/__init__.py +4 -0
- fractal_server/app/routes/admin/v2/accounting.py +98 -0
- fractal_server/app/routes/admin/v2/impersonate.py +35 -0
- fractal_server/app/routes/admin/v2/job.py +5 -13
- fractal_server/app/routes/admin/v2/task.py +1 -1
- fractal_server/app/routes/admin/v2/task_group.py +4 -29
- fractal_server/app/routes/api/__init__.py +1 -1
- fractal_server/app/routes/api/v2/__init__.py +8 -2
- fractal_server/app/routes/api/v2/_aux_functions.py +66 -0
- fractal_server/app/routes/api/v2/_aux_functions_history.py +166 -0
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
- fractal_server/app/routes/api/v2/dataset.py +0 -17
- fractal_server/app/routes/api/v2/history.py +544 -0
- fractal_server/app/routes/api/v2/images.py +31 -43
- fractal_server/app/routes/api/v2/job.py +30 -0
- fractal_server/app/routes/api/v2/project.py +1 -53
- fractal_server/app/routes/api/v2/{status.py → status_legacy.py} +6 -6
- fractal_server/app/routes/api/v2/submit.py +17 -14
- fractal_server/app/routes/api/v2/task.py +3 -10
- fractal_server/app/routes/api/v2/task_collection_custom.py +4 -9
- fractal_server/app/routes/api/v2/task_group.py +2 -22
- fractal_server/app/routes/api/v2/verify_image_types.py +61 -0
- fractal_server/app/routes/api/v2/workflow.py +28 -69
- fractal_server/app/routes/api/v2/workflowtask.py +53 -50
- fractal_server/app/routes/auth/group.py +0 -16
- fractal_server/app/routes/auth/oauth.py +5 -3
- fractal_server/app/routes/aux/__init__.py +0 -20
- fractal_server/app/routes/pagination.py +47 -0
- fractal_server/app/runner/components.py +0 -3
- fractal_server/app/runner/compress_folder.py +57 -29
- fractal_server/app/runner/exceptions.py +4 -0
- fractal_server/app/runner/executors/base_runner.py +157 -0
- fractal_server/app/runner/{v2/_local/_local_config.py → executors/local/get_local_config.py} +7 -9
- fractal_server/app/runner/executors/local/runner.py +248 -0
- fractal_server/app/runner/executors/{slurm → slurm_common}/_batching.py +1 -1
- fractal_server/app/runner/executors/{slurm → slurm_common}/_slurm_config.py +9 -7
- fractal_server/app/runner/executors/slurm_common/base_slurm_runner.py +868 -0
- fractal_server/app/runner/{v2/_slurm_common → executors/slurm_common}/get_slurm_config.py +48 -17
- fractal_server/app/runner/executors/{slurm → slurm_common}/remote.py +36 -47
- fractal_server/app/runner/executors/slurm_common/slurm_job_task_models.py +134 -0
- fractal_server/app/runner/executors/slurm_ssh/runner.py +268 -0
- fractal_server/app/runner/executors/slurm_sudo/__init__.py +0 -0
- fractal_server/app/runner/executors/{slurm/sudo → slurm_sudo}/_subprocess_run_as_user.py +2 -83
- fractal_server/app/runner/executors/slurm_sudo/runner.py +193 -0
- fractal_server/app/runner/extract_archive.py +1 -3
- fractal_server/app/runner/task_files.py +134 -87
- fractal_server/app/runner/v2/__init__.py +0 -395
- fractal_server/app/runner/v2/_local.py +88 -0
- fractal_server/app/runner/v2/{_slurm_ssh/__init__.py → _slurm_ssh.py} +22 -19
- fractal_server/app/runner/v2/{_slurm_sudo/__init__.py → _slurm_sudo.py} +19 -15
- fractal_server/app/runner/v2/db_tools.py +119 -0
- fractal_server/app/runner/v2/runner.py +219 -98
- fractal_server/app/runner/v2/runner_functions.py +491 -189
- fractal_server/app/runner/v2/runner_functions_low_level.py +40 -43
- fractal_server/app/runner/v2/submit_workflow.py +358 -0
- fractal_server/app/runner/v2/task_interface.py +31 -0
- fractal_server/app/schemas/_validators.py +13 -24
- fractal_server/app/schemas/user.py +10 -7
- fractal_server/app/schemas/user_settings.py +9 -21
- fractal_server/app/schemas/v2/__init__.py +10 -1
- fractal_server/app/schemas/v2/accounting.py +18 -0
- fractal_server/app/schemas/v2/dataset.py +12 -94
- fractal_server/app/schemas/v2/dumps.py +26 -9
- fractal_server/app/schemas/v2/history.py +80 -0
- fractal_server/app/schemas/v2/job.py +15 -8
- fractal_server/app/schemas/v2/manifest.py +14 -7
- fractal_server/app/schemas/v2/project.py +9 -7
- fractal_server/app/schemas/v2/status_legacy.py +35 -0
- fractal_server/app/schemas/v2/task.py +72 -77
- fractal_server/app/schemas/v2/task_collection.py +14 -32
- fractal_server/app/schemas/v2/task_group.py +10 -9
- fractal_server/app/schemas/v2/workflow.py +10 -11
- fractal_server/app/schemas/v2/workflowtask.py +2 -21
- fractal_server/app/security/__init__.py +3 -3
- fractal_server/app/security/signup_email.py +2 -2
- fractal_server/config.py +91 -90
- fractal_server/images/tools.py +23 -0
- fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +50 -0
- fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +250 -0
- fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +57 -0
- fractal_server/migrations/versions/c90a7c76e996_job_id_in_history_run.py +41 -0
- fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +36 -0
- fractal_server/migrations/versions/f37aceb45062_make_historyunit_logfile_required.py +39 -0
- fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +120 -0
- fractal_server/ssh/_fabric.py +28 -14
- fractal_server/tasks/v2/local/collect.py +2 -2
- fractal_server/tasks/v2/ssh/collect.py +2 -2
- fractal_server/tasks/v2/templates/2_pip_install.sh +1 -1
- fractal_server/tasks/v2/templates/4_pip_show.sh +1 -1
- fractal_server/tasks/v2/utils_background.py +1 -20
- fractal_server/tasks/v2/utils_database.py +30 -17
- fractal_server/tasks/v2/utils_templates.py +6 -0
- {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/METADATA +4 -4
- {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/RECORD +114 -99
- {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/WHEEL +1 -1
- fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py +0 -126
- fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py +0 -116
- fractal_server/app/runner/executors/slurm/ssh/executor.py +0 -1386
- fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py +0 -71
- fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py +0 -130
- fractal_server/app/runner/executors/slurm/sudo/executor.py +0 -1281
- fractal_server/app/runner/v2/_local/__init__.py +0 -129
- fractal_server/app/runner/v2/_local/_submit_setup.py +0 -52
- fractal_server/app/runner/v2/_local/executor.py +0 -100
- fractal_server/app/runner/v2/_slurm_ssh/_submit_setup.py +0 -83
- fractal_server/app/runner/v2/_slurm_sudo/_submit_setup.py +0 -83
- fractal_server/app/runner/v2/handle_failed_job.py +0 -59
- fractal_server/app/schemas/v2/status.py +0 -16
- /fractal_server/app/{runner/executors/slurm → history}/__init__.py +0 -0
- /fractal_server/app/runner/executors/{slurm/ssh → local}/__init__.py +0 -0
- /fractal_server/app/runner/executors/{slurm/sudo → slurm_common}/__init__.py +0 -0
- /fractal_server/app/runner/executors/{_job_states.py → slurm_common/_job_states.py} +0 -0
- /fractal_server/app/runner/executors/{slurm → slurm_common}/utils_executors.py +0 -0
- /fractal_server/app/runner/{v2/_slurm_common → executors/slurm_ssh}/__init__.py +0 -0
- {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/LICENSE +0 -0
- {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/entry_points.txt +0 -0
@@ -417,3 +417,69 @@ async def clean_app_job_list_v2(
|
|
417
417
|
if job.status == JobStatusTypeV2.SUBMITTED
|
418
418
|
]
|
419
419
|
return submitted_job_ids
|
420
|
+
|
421
|
+
|
422
|
+
async def _get_dataset_or_404(
|
423
|
+
*,
|
424
|
+
dataset_id: int,
|
425
|
+
db: AsyncSession,
|
426
|
+
) -> DatasetV2:
|
427
|
+
"""
|
428
|
+
Get a dataset or raise 404.
|
429
|
+
|
430
|
+
Args:
|
431
|
+
dataset_id:
|
432
|
+
db:
|
433
|
+
"""
|
434
|
+
ds = await db.get(DatasetV2, dataset_id)
|
435
|
+
if ds is None:
|
436
|
+
raise HTTPException(
|
437
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
438
|
+
detail=f"Dataset {dataset_id} not found.",
|
439
|
+
)
|
440
|
+
else:
|
441
|
+
return ds
|
442
|
+
|
443
|
+
|
444
|
+
async def _get_workflow_or_404(
|
445
|
+
*,
|
446
|
+
workflow_id: int,
|
447
|
+
db: AsyncSession,
|
448
|
+
) -> WorkflowV2:
|
449
|
+
"""
|
450
|
+
Get a workflow or raise 404.
|
451
|
+
|
452
|
+
Args:
|
453
|
+
workflow_id:
|
454
|
+
db:
|
455
|
+
"""
|
456
|
+
wf = await db.get(WorkflowV2, workflow_id)
|
457
|
+
if wf is None:
|
458
|
+
raise HTTPException(
|
459
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
460
|
+
detail=f"Workflow {workflow_id} not found.",
|
461
|
+
)
|
462
|
+
else:
|
463
|
+
return wf
|
464
|
+
|
465
|
+
|
466
|
+
async def _get_workflowtask_or_404(
|
467
|
+
*,
|
468
|
+
workflowtask_id: int,
|
469
|
+
db: AsyncSession,
|
470
|
+
) -> WorkflowTaskV2:
|
471
|
+
"""
|
472
|
+
Get a workflow task or raise 404.
|
473
|
+
|
474
|
+
Args:
|
475
|
+
workflowtask_id:
|
476
|
+
db:
|
477
|
+
"""
|
478
|
+
wftask = await db.get(WorkflowTaskV2, workflowtask_id)
|
479
|
+
if wftask is None:
|
480
|
+
raise HTTPException(
|
481
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
482
|
+
detail=f"WorkflowTask {workflowtask_id} not found.",
|
483
|
+
)
|
484
|
+
else:
|
485
|
+
return wftask
|
@@ -0,0 +1,166 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from fastapi import HTTPException
|
5
|
+
from fastapi import status
|
6
|
+
|
7
|
+
from fractal_server.app.db import AsyncSession
|
8
|
+
from fractal_server.app.models import WorkflowTaskV2
|
9
|
+
from fractal_server.app.models.v2 import DatasetV2
|
10
|
+
from fractal_server.app.models.v2 import HistoryRun
|
11
|
+
from fractal_server.app.models.v2 import HistoryUnit
|
12
|
+
from fractal_server.app.models.v2 import WorkflowV2
|
13
|
+
from fractal_server.app.routes.api.v2._aux_functions import _get_dataset_or_404
|
14
|
+
from fractal_server.app.routes.api.v2._aux_functions import (
|
15
|
+
_get_project_check_owner,
|
16
|
+
)
|
17
|
+
from fractal_server.app.routes.api.v2._aux_functions import (
|
18
|
+
_get_workflow_or_404,
|
19
|
+
)
|
20
|
+
from fractal_server.app.routes.api.v2._aux_functions import (
|
21
|
+
_get_workflowtask_or_404,
|
22
|
+
)
|
23
|
+
from fractal_server.logger import set_logger
|
24
|
+
|
25
|
+
|
26
|
+
logger = set_logger(__name__)
|
27
|
+
|
28
|
+
|
29
|
+
async def get_history_unit_or_404(
|
30
|
+
*, history_unit_id: int, db: AsyncSession
|
31
|
+
) -> HistoryUnit:
|
32
|
+
"""
|
33
|
+
Get an existing HistoryUnit or raise a 404.
|
34
|
+
|
35
|
+
Arguments:
|
36
|
+
history_unit_id: The `HistoryUnit` id
|
37
|
+
db: An asynchronous db session
|
38
|
+
"""
|
39
|
+
history_unit = await db.get(HistoryUnit, history_unit_id)
|
40
|
+
if history_unit is None:
|
41
|
+
raise HTTPException(
|
42
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
43
|
+
detail=f"HistoryUnit {history_unit_id} not found",
|
44
|
+
)
|
45
|
+
return history_unit
|
46
|
+
|
47
|
+
|
48
|
+
async def get_history_run_or_404(
|
49
|
+
*, history_run_id: int, db: AsyncSession
|
50
|
+
) -> HistoryRun:
|
51
|
+
"""
|
52
|
+
Get an existing HistoryRun or raise a 404.
|
53
|
+
|
54
|
+
Arguments:
|
55
|
+
history_run_id:
|
56
|
+
db:
|
57
|
+
"""
|
58
|
+
history_run = await db.get(HistoryRun, history_run_id)
|
59
|
+
if history_run is None:
|
60
|
+
raise HTTPException(
|
61
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
62
|
+
detail=f"HistoryRun {history_run_id} not found",
|
63
|
+
)
|
64
|
+
return history_run
|
65
|
+
|
66
|
+
|
67
|
+
def read_log_file(
|
68
|
+
*,
|
69
|
+
logfile: str | None,
|
70
|
+
wftask: WorkflowTaskV2,
|
71
|
+
dataset_id: int,
|
72
|
+
):
|
73
|
+
if logfile is None or not Path(logfile).exists():
|
74
|
+
logger.debug(
|
75
|
+
f"Logs for task '{wftask.task.name}' in dataset "
|
76
|
+
f"{dataset_id} are not available ({logfile=})."
|
77
|
+
)
|
78
|
+
return (
|
79
|
+
f"Logs for task '{wftask.task.name}' in dataset "
|
80
|
+
f"{dataset_id} are not available."
|
81
|
+
)
|
82
|
+
|
83
|
+
try:
|
84
|
+
with open(logfile, "r") as f:
|
85
|
+
return f.read()
|
86
|
+
except Exception as e:
|
87
|
+
return (
|
88
|
+
f"Error while retrieving logs for task '{wftask.task.name}' "
|
89
|
+
f"in dataset {dataset_id}. Original error: {str(e)}."
|
90
|
+
)
|
91
|
+
|
92
|
+
|
93
|
+
async def _verify_workflow_and_dataset_access(
|
94
|
+
*,
|
95
|
+
project_id: int,
|
96
|
+
workflow_id: int,
|
97
|
+
dataset_id: int,
|
98
|
+
user_id: int,
|
99
|
+
db: AsyncSession,
|
100
|
+
) -> dict[Literal["dataset", "workflow"], DatasetV2 | WorkflowV2]:
|
101
|
+
"""
|
102
|
+
Verify user access to a dataset/workflow pair.
|
103
|
+
|
104
|
+
Args:
|
105
|
+
dataset_id:
|
106
|
+
workflow_task_id:
|
107
|
+
user_id:
|
108
|
+
db:
|
109
|
+
"""
|
110
|
+
await _get_project_check_owner(
|
111
|
+
project_id=project_id,
|
112
|
+
user_id=user_id,
|
113
|
+
db=db,
|
114
|
+
)
|
115
|
+
workflow = await _get_workflow_or_404(
|
116
|
+
workflow_id=workflow_id,
|
117
|
+
db=db,
|
118
|
+
)
|
119
|
+
if workflow.project_id != project_id:
|
120
|
+
raise HTTPException(
|
121
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
122
|
+
detail="Workflow does not belong to expected project.",
|
123
|
+
)
|
124
|
+
dataset = await _get_dataset_or_404(
|
125
|
+
dataset_id=dataset_id,
|
126
|
+
db=db,
|
127
|
+
)
|
128
|
+
if dataset.project_id != project_id:
|
129
|
+
raise HTTPException(
|
130
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
131
|
+
detail="Dataset does not belong to expected project.",
|
132
|
+
)
|
133
|
+
|
134
|
+
return dict(dataset=dataset, workflow=workflow)
|
135
|
+
|
136
|
+
|
137
|
+
async def get_wftask_check_owner(
|
138
|
+
*,
|
139
|
+
project_id: int,
|
140
|
+
dataset_id: int,
|
141
|
+
workflowtask_id: int,
|
142
|
+
user_id: int,
|
143
|
+
db: AsyncSession,
|
144
|
+
) -> WorkflowTaskV2:
|
145
|
+
"""
|
146
|
+
Verify user access for the history of this dataset and workflowtask.
|
147
|
+
|
148
|
+
Args:
|
149
|
+
project_id:
|
150
|
+
dataset_id:
|
151
|
+
workflow_task_id:
|
152
|
+
user_id:
|
153
|
+
db:
|
154
|
+
"""
|
155
|
+
wftask = await _get_workflowtask_or_404(
|
156
|
+
workflowtask_id=workflowtask_id,
|
157
|
+
db=db,
|
158
|
+
)
|
159
|
+
await _verify_workflow_and_dataset_access(
|
160
|
+
project_id=project_id,
|
161
|
+
dataset_id=dataset_id,
|
162
|
+
workflow_id=wftask.workflow_id,
|
163
|
+
user_id=user_id,
|
164
|
+
db=db,
|
165
|
+
)
|
166
|
+
return wftask
|
@@ -55,7 +55,7 @@ async def get_package_version_from_pypi(
|
|
55
55
|
f"A TimeoutException occurred while getting {url}.\n"
|
56
56
|
f"Original error: {str(e)}."
|
57
57
|
)
|
58
|
-
logger.
|
58
|
+
logger.warning(error_msg)
|
59
59
|
raise HTTPException(
|
60
60
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
61
61
|
detail=error_msg,
|
@@ -65,7 +65,7 @@ async def get_package_version_from_pypi(
|
|
65
65
|
f"An unknown error occurred while getting {url}. "
|
66
66
|
f"Original error: {str(e)}."
|
67
67
|
)
|
68
|
-
logger.
|
68
|
+
logger.warning(error_msg)
|
69
69
|
raise HTTPException(
|
70
70
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
71
71
|
detail=error_msg,
|
@@ -85,7 +85,7 @@ async def get_package_version_from_pypi(
|
|
85
85
|
latest_version = response_data["info"]["version"]
|
86
86
|
available_releases = response_data["releases"].keys()
|
87
87
|
except KeyError as e:
|
88
|
-
logger.
|
88
|
+
logger.warning(
|
89
89
|
f"A KeyError occurred while getting {url}. "
|
90
90
|
f"Original error: {str(e)}."
|
91
91
|
)
|
@@ -47,7 +47,6 @@ async def create_dataset(
|
|
47
47
|
)
|
48
48
|
|
49
49
|
if dataset.zarr_dir is None:
|
50
|
-
|
51
50
|
if user.settings.project_dir is None:
|
52
51
|
raise HTTPException(
|
53
52
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
@@ -91,7 +90,6 @@ async def create_dataset(
|
|
91
90
|
)
|
92
91
|
async def read_dataset_list(
|
93
92
|
project_id: int,
|
94
|
-
history: bool = True,
|
95
93
|
user: UserOAuth = Depends(current_active_user),
|
96
94
|
db: AsyncSession = Depends(get_async_db),
|
97
95
|
) -> Optional[list[DatasetReadV2]]:
|
@@ -110,9 +108,6 @@ async def read_dataset_list(
|
|
110
108
|
res = await db.execute(stm)
|
111
109
|
dataset_list = res.scalars().all()
|
112
110
|
await db.close()
|
113
|
-
if not history:
|
114
|
-
for ds in dataset_list:
|
115
|
-
setattr(ds, "history", [])
|
116
111
|
return dataset_list
|
117
112
|
|
118
113
|
|
@@ -217,14 +212,6 @@ async def delete_dataset(
|
|
217
212
|
),
|
218
213
|
)
|
219
214
|
|
220
|
-
# Cascade operations: set foreign-keys to null for jobs which are in
|
221
|
-
# relationship with the current dataset
|
222
|
-
stm = select(JobV2).where(JobV2.dataset_id == dataset_id)
|
223
|
-
res = await db.execute(stm)
|
224
|
-
jobs = res.scalars().all()
|
225
|
-
for job in jobs:
|
226
|
-
job.dataset_id = None
|
227
|
-
|
228
215
|
# Delete dataset
|
229
216
|
await db.delete(dataset)
|
230
217
|
await db.commit()
|
@@ -234,7 +221,6 @@ async def delete_dataset(
|
|
234
221
|
|
235
222
|
@router.get("/dataset/", response_model=list[DatasetReadV2])
|
236
223
|
async def get_user_datasets(
|
237
|
-
history: bool = True,
|
238
224
|
user: UserOAuth = Depends(current_active_user),
|
239
225
|
db: AsyncSession = Depends(get_async_db),
|
240
226
|
) -> list[DatasetReadV2]:
|
@@ -249,9 +235,6 @@ async def get_user_datasets(
|
|
249
235
|
res = await db.execute(stm)
|
250
236
|
dataset_list = res.scalars().all()
|
251
237
|
await db.close()
|
252
|
-
if not history:
|
253
|
-
for ds in dataset_list:
|
254
|
-
setattr(ds, "history", [])
|
255
238
|
return dataset_list
|
256
239
|
|
257
240
|
|