fractal-server 2.0.0a0__py3-none-any.whl → 2.0.0a1__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/app/models/__init__.py +0 -1
- fractal_server/app/models/v1/__init__.py +1 -0
- fractal_server/app/routes/admin/v2.py +0 -1
- fractal_server/app/routes/api/v1/job.py +3 -3
- fractal_server/app/routes/api/v2/__init__.py +2 -2
- fractal_server/app/routes/api/v2/dataset.py +0 -1
- fractal_server/app/routes/api/v2/images.py +4 -9
- fractal_server/app/routes/api/v2/project.py +0 -3
- fractal_server/app/routes/api/v2/{apply.py → submit.py} +1 -1
- fractal_server/app/routes/api/v2/workflow.py +0 -1
- fractal_server/app/runner/executors/slurm/executor.py +23 -10
- fractal_server/app/runner/task_files.py +0 -2
- fractal_server/app/runner/v1/__init__.py +2 -2
- fractal_server/app/runner/v1/_local/__init__.py +1 -1
- fractal_server/app/runner/{executors/local → v1/_local}/executor.py +2 -2
- fractal_server/app/runner/v2/__init__.py +0 -1
- fractal_server/app/runner/v2/_local/__init__.py +1 -3
- fractal_server/app/runner/v2/_local/executor.py +100 -0
- fractal_server/app/runner/v2/_slurm/__init__.py +68 -86
- fractal_server/app/runner/v2/deduplicate_list.py +7 -9
- fractal_server/app/runner/v2/merge_outputs.py +1 -4
- fractal_server/app/runner/v2/runner.py +19 -16
- fractal_server/app/runner/v2/runner_functions.py +14 -12
- fractal_server/app/runner/v2/runner_functions_low_level.py +1 -1
- fractal_server/app/schemas/v2/dataset.py +2 -1
- fractal_server/app/schemas/v2/job.py +2 -1
- fractal_server/app/schemas/v2/manifest.py +51 -1
- fractal_server/app/schemas/v2/project.py +2 -1
- fractal_server/app/schemas/v2/task.py +2 -3
- fractal_server/app/schemas/v2/workflow.py +2 -1
- fractal_server/app/schemas/v2/workflowtask.py +2 -1
- fractal_server/images/__init__.py +2 -50
- fractal_server/images/models.py +50 -0
- fractal_server/images/tools.py +35 -36
- fractal_server/migrations/env.py +0 -2
- fractal_server/migrations/versions/{56af171b0159_v2.py → d71e732236cd_v2.py} +29 -7
- fractal_server/tasks/v2/background_operations.py +0 -1
- {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/METADATA +1 -1
- {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/RECORD +44 -50
- fractal_server/app/runner/executors/local/__init__.py +0 -3
- fractal_server/migrations/versions/4b35c5cefbe3_tmp_is_v2_compatible.py +0 -39
- fractal_server/migrations/versions/876f28db9d4e_tmp_split_task_and_wftask_meta.py +0 -68
- fractal_server/migrations/versions/974c802f0dd0_tmp_workflowtaskv2_type_in_db.py +0 -37
- fractal_server/migrations/versions/9cd305cd6023_tmp_workflowtaskv2.py +0 -40
- fractal_server/migrations/versions/a6231ed6273c_tmp_args_schemas_in_taskv2.py +0 -42
- fractal_server/migrations/versions/b9e9eed9d442_tmp_taskv2_type.py +0 -37
- fractal_server/migrations/versions/e3e639454d4b_tmp_make_task_meta_non_optional.py +0 -50
- /fractal_server/app/runner/{v2/components.py → components.py} +0 -0
- {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/LICENSE +0 -0
- {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/WHEEL +0 -0
- {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/entry_points.txt +0 -0
fractal_server/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "2.0.
|
1
|
+
__VERSION__ = "2.0.0a1"
|
@@ -4,6 +4,7 @@
|
|
4
4
|
from .dataset import Dataset # noqa: F401
|
5
5
|
from .dataset import Resource # noqa: F401
|
6
6
|
from .job import ApplyWorkflow # noqa: F403, F401
|
7
|
+
from .job import JobStatusTypeV1 # noqa: F401, F403
|
7
8
|
from .project import Project # noqa: F403, F401
|
8
9
|
from .task import Task # noqa: F403, F401
|
9
10
|
from .workflow import Workflow # noqa: F401, F403
|
@@ -10,9 +10,9 @@ 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 JobStatusTypeV1
|
15
|
-
from ....models import Project
|
13
|
+
from ....models.v1 import ApplyWorkflow
|
14
|
+
from ....models.v1 import JobStatusTypeV1
|
15
|
+
from ....models.v1 import Project
|
16
16
|
from ....runner.filenames import WORKFLOW_LOG_FILENAME
|
17
17
|
from ....schemas.v1 import ApplyWorkflowReadV1
|
18
18
|
from ....security import current_active_user
|
@@ -3,11 +3,11 @@
|
|
3
3
|
"""
|
4
4
|
from fastapi import APIRouter
|
5
5
|
|
6
|
-
from .apply import router as runner_router_v2
|
7
6
|
from .dataset import router as dataset_router_v2
|
8
7
|
from .images import router as images_routes_v2
|
9
8
|
from .job import router as job_router_v2
|
10
9
|
from .project import router as project_router_v2
|
10
|
+
from .submit import router as submit_job_router_v2
|
11
11
|
from .task import router as task_router_v2
|
12
12
|
from .task_collection import router as task_collection_router_v2
|
13
13
|
from .workflow import router as workflow_router_v2
|
@@ -19,7 +19,7 @@ router_api_v2.include_router(dataset_router_v2, tags=["V2 Dataset"])
|
|
19
19
|
router_api_v2.include_router(job_router_v2, tags=["V2 Job"])
|
20
20
|
router_api_v2.include_router(images_routes_v2, tags=["V2 Images"])
|
21
21
|
router_api_v2.include_router(project_router_v2, tags=["V2 Project"])
|
22
|
-
router_api_v2.include_router(
|
22
|
+
router_api_v2.include_router(submit_job_router_v2, tags=["V2 Submit Job"])
|
23
23
|
router_api_v2.include_router(task_router_v2, prefix="/task", tags=["V2 Task"])
|
24
24
|
router_api_v2.include_router(
|
25
25
|
task_collection_router_v2, prefix="/task", tags=["V2 Task Collection"]
|
@@ -17,7 +17,7 @@ from fractal_server.app.security import current_active_user
|
|
17
17
|
from fractal_server.app.security import User
|
18
18
|
from fractal_server.images import Filters
|
19
19
|
from fractal_server.images import SingleImage
|
20
|
-
from fractal_server.images.tools import
|
20
|
+
from fractal_server.images.tools import match_filter
|
21
21
|
|
22
22
|
router = APIRouter()
|
23
23
|
|
@@ -68,7 +68,6 @@ async def post_new_image(
|
|
68
68
|
dataset.images.append(new_image.dict())
|
69
69
|
flag_modified(dataset, "images")
|
70
70
|
|
71
|
-
await db.merge(dataset)
|
72
71
|
await db.commit()
|
73
72
|
|
74
73
|
return Response(status_code=status.HTTP_201_CREATED)
|
@@ -106,9 +105,7 @@ async def query_dataset_images(
|
|
106
105
|
images = [
|
107
106
|
image
|
108
107
|
for image in images
|
109
|
-
if
|
110
|
-
SingleImage(**image), Filters(**dataset.filters)
|
111
|
-
)
|
108
|
+
if match_filter(image, Filters(**dataset.filters))
|
112
109
|
]
|
113
110
|
|
114
111
|
attributes = {}
|
@@ -138,8 +135,8 @@ async def query_dataset_images(
|
|
138
135
|
images = [
|
139
136
|
image
|
140
137
|
for image in images
|
141
|
-
if
|
142
|
-
|
138
|
+
if match_filter(
|
139
|
+
image,
|
143
140
|
Filters(**query.filters.dict()),
|
144
141
|
)
|
145
142
|
]
|
@@ -159,7 +156,6 @@ async def query_dataset_images(
|
|
159
156
|
|
160
157
|
if total_count == 0:
|
161
158
|
page = 1
|
162
|
-
page_size = 0
|
163
159
|
else:
|
164
160
|
last_page = (total_count // page_size) + (total_count % page_size > 0)
|
165
161
|
if page > last_page:
|
@@ -206,7 +202,6 @@ async def delete_dataset_images(
|
|
206
202
|
dataset.images.remove(image_to_remove)
|
207
203
|
flag_modified(dataset, "images")
|
208
204
|
|
209
|
-
await db.merge(dataset)
|
210
205
|
await db.commit()
|
211
206
|
|
212
207
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
@@ -167,7 +167,6 @@ async def delete_project(
|
|
167
167
|
jobs = res.scalars().all()
|
168
168
|
for job in jobs:
|
169
169
|
job.workflow_id = None
|
170
|
-
await db.merge(job)
|
171
170
|
# Delete workflow
|
172
171
|
await db.delete(wf)
|
173
172
|
await db.commit()
|
@@ -184,7 +183,6 @@ async def delete_project(
|
|
184
183
|
jobs = res.scalars().all()
|
185
184
|
for job in jobs:
|
186
185
|
job.dataset_id = None
|
187
|
-
await db.merge(job)
|
188
186
|
# Delete dataset
|
189
187
|
await db.delete(ds)
|
190
188
|
await db.commit()
|
@@ -195,7 +193,6 @@ async def delete_project(
|
|
195
193
|
jobs = res.scalars().all()
|
196
194
|
for job in jobs:
|
197
195
|
job.project_id = None
|
198
|
-
await db.merge(job)
|
199
196
|
|
200
197
|
await db.commit()
|
201
198
|
|
@@ -46,6 +46,7 @@ from ._subprocess_run_as_user import _glob_as_user_strict
|
|
46
46
|
from ._subprocess_run_as_user import _path_exists_as_user
|
47
47
|
from ._subprocess_run_as_user import _run_command_as_user
|
48
48
|
from fractal_server import __VERSION__
|
49
|
+
from fractal_server.app.runner.components import _COMPONENT_KEY_
|
49
50
|
|
50
51
|
|
51
52
|
logger = set_logger(__name__)
|
@@ -544,7 +545,7 @@ class FractalSlurmExecutor(SlurmExecutor):
|
|
544
545
|
single_task_submission: bool = False,
|
545
546
|
args: Optional[Sequence[Any]] = None,
|
546
547
|
kwargs: Optional[dict] = None,
|
547
|
-
components: list[Any] = None,
|
548
|
+
components: Optional[list[Any]] = None,
|
548
549
|
) -> Future:
|
549
550
|
"""
|
550
551
|
Submit a multi-task job to the pool, where each task is handled via the
|
@@ -580,6 +581,10 @@ class FractalSlurmExecutor(SlurmExecutor):
|
|
580
581
|
|
581
582
|
# Define slurm-job-related files
|
582
583
|
if single_task_submission:
|
584
|
+
if components is not None:
|
585
|
+
raise ValueError(
|
586
|
+
f"{single_task_submission=} but components is not None"
|
587
|
+
)
|
583
588
|
job = SlurmJob(
|
584
589
|
slurm_file_prefix=slurm_file_prefix,
|
585
590
|
num_tasks_tot=1,
|
@@ -603,15 +608,23 @@ class FractalSlurmExecutor(SlurmExecutor):
|
|
603
608
|
num_tasks_tot=num_tasks_tot,
|
604
609
|
slurm_config=slurm_config,
|
605
610
|
)
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
611
|
+
|
612
|
+
_prefixes = []
|
613
|
+
for component in components:
|
614
|
+
if isinstance(component, dict):
|
615
|
+
# This is needed for V2
|
616
|
+
actual_component = component.get(_COMPONENT_KEY_, None)
|
617
|
+
else:
|
618
|
+
actual_component = component
|
619
|
+
_prefixes.append(
|
620
|
+
get_task_file_paths(
|
621
|
+
workflow_dir=task_files.workflow_dir,
|
622
|
+
workflow_dir_user=task_files.workflow_dir_user,
|
623
|
+
task_order=task_files.task_order,
|
624
|
+
component=actual_component,
|
625
|
+
).file_prefix
|
626
|
+
)
|
627
|
+
job.wftask_file_prefixes = tuple(_prefixes)
|
615
628
|
|
616
629
|
# Define I/O pickle file names/paths
|
617
630
|
job.input_pickle_files = tuple(
|
@@ -34,8 +34,8 @@ from ...schemas.v1 import JobStatusTypeV1
|
|
34
34
|
from ..exceptions import JobExecutionError
|
35
35
|
from ..exceptions import TaskExecutionError
|
36
36
|
from ..filenames import WORKFLOW_LOG_FILENAME
|
37
|
-
from
|
38
|
-
from
|
37
|
+
from ._local import process_workflow as local_process_workflow
|
38
|
+
from ._slurm import process_workflow as slurm_process_workflow
|
39
39
|
from .common import close_job_logger
|
40
40
|
from .common import validate_workflow_compatibility # noqa: F401
|
41
41
|
from .handle_failed_job import assemble_history_failed_job
|
@@ -25,11 +25,11 @@ from typing import Optional
|
|
25
25
|
|
26
26
|
from ....models import Workflow # FIXME: this is v1 specific
|
27
27
|
from ...async_wrap import async_wrap
|
28
|
-
from ...executors.local.executor import FractalThreadPoolExecutor
|
29
28
|
from ...set_start_and_last_task_index import set_start_and_last_task_index
|
30
29
|
from .._common import execute_tasks # FIXME: this is v1 specific
|
31
30
|
from ..common import TaskParameters # FIXME: this is v1 specific
|
32
31
|
from ._submit_setup import _local_submit_setup
|
32
|
+
from .executor import FractalThreadPoolExecutor
|
33
33
|
|
34
34
|
|
35
35
|
def _process_workflow(
|
@@ -18,8 +18,8 @@ from typing import Iterable
|
|
18
18
|
from typing import Optional
|
19
19
|
from typing import Sequence
|
20
20
|
|
21
|
-
from
|
22
|
-
from
|
21
|
+
from ._local_config import get_default_local_backend_config
|
22
|
+
from ._local_config import LocalBackendConfig
|
23
23
|
|
24
24
|
|
25
25
|
class FractalThreadPoolExecutor(ThreadPoolExecutor):
|
@@ -31,7 +31,6 @@ from ._slurm import process_workflow as slurm_process_workflow
|
|
31
31
|
from .handle_failed_job import assemble_filters_failed_job
|
32
32
|
from .handle_failed_job import assemble_history_failed_job
|
33
33
|
from .handle_failed_job import assemble_images_failed_job
|
34
|
-
from .runner import execute_tasks_v2 # noqa
|
35
34
|
from fractal_server import __VERSION__
|
36
35
|
|
37
36
|
_backends = {}
|
@@ -25,12 +25,10 @@ from typing import Optional
|
|
25
25
|
from ....models.v2 import DatasetV2
|
26
26
|
from ....models.v2 import WorkflowV2
|
27
27
|
from ...async_wrap import async_wrap
|
28
|
-
from ...executors.local.executor import FractalThreadPoolExecutor
|
29
28
|
from ...set_start_and_last_task_index import set_start_and_last_task_index
|
30
29
|
from ..runner import execute_tasks_v2
|
31
30
|
from ._submit_setup import _local_submit_setup
|
32
|
-
|
33
|
-
# from typing import Any
|
31
|
+
from .executor import FractalThreadPoolExecutor
|
34
32
|
|
35
33
|
|
36
34
|
def _process_workflow(
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# Copyright 2022 (C) Friedrich Miescher Institute for Biomedical Research and
|
2
|
+
# University of Zurich
|
3
|
+
#
|
4
|
+
# Original authors:
|
5
|
+
# Tommaso Comparin <tommaso.comparin@exact-lab.it>
|
6
|
+
#
|
7
|
+
# This file is part of Fractal and was originally developed by eXact lab S.r.l.
|
8
|
+
# <exact-lab.it> under contract with Liberali Lab from the Friedrich Miescher
|
9
|
+
# Institute for Biomedical Research and Pelkmans Lab from the University of
|
10
|
+
# Zurich.
|
11
|
+
"""
|
12
|
+
Custom version of Python
|
13
|
+
[ThreadPoolExecutor](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor)).
|
14
|
+
"""
|
15
|
+
from concurrent.futures import ThreadPoolExecutor
|
16
|
+
from typing import Callable
|
17
|
+
from typing import Iterable
|
18
|
+
from typing import Optional
|
19
|
+
from typing import Sequence
|
20
|
+
|
21
|
+
from ._local_config import get_default_local_backend_config
|
22
|
+
from ._local_config import LocalBackendConfig
|
23
|
+
|
24
|
+
|
25
|
+
class FractalThreadPoolExecutor(ThreadPoolExecutor):
|
26
|
+
"""
|
27
|
+
Custom version of
|
28
|
+
[ThreadPoolExecutor](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor))
|
29
|
+
that overrides the `submit` and `map` methods
|
30
|
+
"""
|
31
|
+
|
32
|
+
def submit(
|
33
|
+
self,
|
34
|
+
*args,
|
35
|
+
local_backend_config: Optional[LocalBackendConfig] = None,
|
36
|
+
**kwargs,
|
37
|
+
):
|
38
|
+
"""
|
39
|
+
Compared to the `ThreadPoolExecutor` method, here we accept an addition
|
40
|
+
keyword argument (`local_backend_config`), which is then simply
|
41
|
+
ignored.
|
42
|
+
"""
|
43
|
+
return super().submit(*args, **kwargs)
|
44
|
+
|
45
|
+
def map(
|
46
|
+
self,
|
47
|
+
fn: Callable,
|
48
|
+
*iterables: Sequence[Iterable],
|
49
|
+
local_backend_config: Optional[LocalBackendConfig] = None,
|
50
|
+
):
|
51
|
+
"""
|
52
|
+
Custom version of the `Executor.map` method
|
53
|
+
|
54
|
+
The main change with the respect to the original `map` method is that
|
55
|
+
the list of tasks to be executed is split into chunks, and then
|
56
|
+
`super().map` is called (sequentially) on each chunk. The goal of this
|
57
|
+
change is to limit parallelism, e.g. due to limited computational
|
58
|
+
resources.
|
59
|
+
|
60
|
+
Other changes from the `concurrent.futures` `map` method:
|
61
|
+
|
62
|
+
1. Removed `timeout` argument;
|
63
|
+
2. Removed `chunksize`;
|
64
|
+
3. All iterators (both inputs and output ones) are transformed into
|
65
|
+
lists.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
fn: A callable function.
|
69
|
+
iterables: The argument iterables (one iterable per argument of
|
70
|
+
`fn`).
|
71
|
+
local_backend_config: The backend configuration, needed to extract
|
72
|
+
`parallel_tasks_per_job`.
|
73
|
+
"""
|
74
|
+
|
75
|
+
# Preliminary check
|
76
|
+
iterable_lengths = [len(it) for it in iterables]
|
77
|
+
if not len(set(iterable_lengths)) == 1:
|
78
|
+
raise ValueError("Iterables have different lengths.")
|
79
|
+
|
80
|
+
# Set total number of arguments
|
81
|
+
n_elements = len(iterables[0])
|
82
|
+
|
83
|
+
# Set parallel_tasks_per_job
|
84
|
+
if local_backend_config is None:
|
85
|
+
local_backend_config = get_default_local_backend_config()
|
86
|
+
parallel_tasks_per_job = local_backend_config.parallel_tasks_per_job
|
87
|
+
if parallel_tasks_per_job is None:
|
88
|
+
parallel_tasks_per_job = n_elements
|
89
|
+
|
90
|
+
# Execute tasks, in chunks of size parallel_tasks_per_job
|
91
|
+
results = []
|
92
|
+
for ind_chunk in range(0, n_elements, parallel_tasks_per_job):
|
93
|
+
chunk_iterables = [
|
94
|
+
it[ind_chunk : ind_chunk + parallel_tasks_per_job] # noqa
|
95
|
+
for it in iterables
|
96
|
+
]
|
97
|
+
map_iter = super().map(fn, *chunk_iterables)
|
98
|
+
results.extend(list(map_iter))
|
99
|
+
|
100
|
+
return iter(results)
|
@@ -21,11 +21,13 @@ from typing import Any
|
|
21
21
|
from typing import Optional
|
22
22
|
from typing import Union
|
23
23
|
|
24
|
-
from
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
from ....models.v2 import DatasetV2
|
25
|
+
from ....models.v2 import WorkflowV2
|
26
|
+
from ...async_wrap import async_wrap
|
27
|
+
from ...executors.slurm.executor import FractalSlurmExecutor
|
28
|
+
from ...set_start_and_last_task_index import set_start_and_last_task_index
|
29
|
+
from ..runner import execute_tasks_v2
|
30
|
+
from ._submit_setup import _slurm_submit_setup
|
29
31
|
|
30
32
|
# from .._common import execute_tasks
|
31
33
|
# from ..common import async_wrap
|
@@ -36,10 +38,7 @@ from fractal_server.app.models.v2 import WorkflowV2
|
|
36
38
|
def _process_workflow(
|
37
39
|
*,
|
38
40
|
workflow: WorkflowV2,
|
39
|
-
|
40
|
-
output_path: Path,
|
41
|
-
input_metadata: dict[str, Any],
|
42
|
-
input_history: list[dict[str, Any]],
|
41
|
+
dataset: DatasetV2,
|
43
42
|
logger_name: str,
|
44
43
|
workflow_dir: Path,
|
45
44
|
workflow_dir_user: Path,
|
@@ -55,103 +54,86 @@ def _process_workflow(
|
|
55
54
|
|
56
55
|
This function initialises the a FractalSlurmExecutor, setting logging,
|
57
56
|
workflow working dir and user to impersonate. It then schedules the
|
58
|
-
workflow tasks and returns the
|
57
|
+
workflow tasks and returns the new dataset attributes
|
59
58
|
|
60
59
|
Cf. [process_workflow][fractal_server.app.runner._local.process_workflow]
|
61
60
|
|
62
61
|
Returns:
|
63
|
-
|
62
|
+
new_dataset_attributes:
|
64
63
|
"""
|
65
64
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
# ),
|
97
|
-
# workflow_dir=workflow_dir,
|
98
|
-
# workflow_dir_user=workflow_dir_user,
|
99
|
-
# submit_setup_call=_slurm_submit_setup,
|
100
|
-
# logger_name=logger_name,
|
101
|
-
# )
|
102
|
-
# output_dataset_metadata_history = dict(
|
103
|
-
# metadata=output_task_pars.metadata, history=output_task_pars.history
|
104
|
-
# )
|
105
|
-
# return output_dataset_metadata_history
|
65
|
+
if not slurm_user:
|
66
|
+
raise RuntimeError(
|
67
|
+
"slurm_user argument is required, for slurm backend"
|
68
|
+
)
|
69
|
+
|
70
|
+
if isinstance(worker_init, str):
|
71
|
+
worker_init = worker_init.split("\n")
|
72
|
+
|
73
|
+
with FractalSlurmExecutor(
|
74
|
+
debug=True,
|
75
|
+
keep_logs=True,
|
76
|
+
slurm_user=slurm_user,
|
77
|
+
user_cache_dir=user_cache_dir,
|
78
|
+
working_dir=workflow_dir,
|
79
|
+
working_dir_user=workflow_dir_user,
|
80
|
+
common_script_lines=worker_init,
|
81
|
+
slurm_account=slurm_account,
|
82
|
+
) as executor:
|
83
|
+
new_dataset_attributes = execute_tasks_v2(
|
84
|
+
wf_task_list=workflow.task_list[
|
85
|
+
first_task_index : (last_task_index + 1) # noqa
|
86
|
+
], # noqa
|
87
|
+
dataset=dataset,
|
88
|
+
executor=executor,
|
89
|
+
workflow_dir=workflow_dir,
|
90
|
+
workflow_dir_user=workflow_dir_user,
|
91
|
+
logger_name=logger_name,
|
92
|
+
submit_setup_call=_slurm_submit_setup,
|
93
|
+
)
|
94
|
+
return new_dataset_attributes
|
106
95
|
|
107
96
|
|
108
97
|
async def process_workflow(
|
109
98
|
*,
|
110
99
|
workflow: WorkflowV2,
|
111
|
-
|
112
|
-
output_path: Path,
|
113
|
-
input_metadata: dict[str, Any],
|
114
|
-
input_history: list[dict[str, Any]],
|
115
|
-
logger_name: str,
|
100
|
+
dataset: DatasetV2,
|
116
101
|
workflow_dir: Path,
|
117
102
|
workflow_dir_user: Optional[Path] = None,
|
103
|
+
first_task_index: Optional[int] = None,
|
104
|
+
last_task_index: Optional[int] = None,
|
105
|
+
logger_name: str,
|
106
|
+
# Slurm-specific
|
118
107
|
user_cache_dir: Optional[str] = None,
|
119
108
|
slurm_user: Optional[str] = None,
|
120
109
|
slurm_account: Optional[str] = None,
|
121
110
|
worker_init: Optional[str] = None,
|
122
|
-
|
123
|
-
last_task_index: Optional[int] = None,
|
124
|
-
) -> dict[str, Any]:
|
111
|
+
) -> dict:
|
125
112
|
"""
|
126
113
|
Process workflow (SLURM backend public interface)
|
127
114
|
|
128
115
|
Cf. [process_workflow][fractal_server.app.runner._local.process_workflow]
|
129
116
|
"""
|
130
117
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
# worker_init=worker_init,
|
154
|
-
# first_task_index=first_task_index,
|
155
|
-
# last_task_index=last_task_index,
|
156
|
-
# )
|
157
|
-
# return output_dataset_metadata_history
|
118
|
+
# Set values of first_task_index and last_task_index
|
119
|
+
num_tasks = len(workflow.task_list)
|
120
|
+
first_task_index, last_task_index = set_start_and_last_task_index(
|
121
|
+
num_tasks,
|
122
|
+
first_task_index=first_task_index,
|
123
|
+
last_task_index=last_task_index,
|
124
|
+
)
|
125
|
+
|
126
|
+
new_dataset_attributes = await async_wrap(_process_workflow)(
|
127
|
+
workflow=workflow,
|
128
|
+
dataset=dataset,
|
129
|
+
logger_name=logger_name,
|
130
|
+
workflow_dir=workflow_dir,
|
131
|
+
workflow_dir_user=workflow_dir_user,
|
132
|
+
first_task_index=first_task_index,
|
133
|
+
last_task_index=last_task_index,
|
134
|
+
user_cache_dir=user_cache_dir,
|
135
|
+
slurm_user=slurm_user,
|
136
|
+
slurm_account=slurm_account,
|
137
|
+
worker_init=worker_init,
|
138
|
+
)
|
139
|
+
return new_dataset_attributes
|
@@ -1,7 +1,5 @@
|
|
1
1
|
from typing import TypeVar
|
2
2
|
|
3
|
-
from pydantic.main import ModelMetaclass
|
4
|
-
|
5
3
|
from ....images import SingleImage
|
6
4
|
from .task_interface import InitArgsModel
|
7
5
|
|
@@ -9,16 +7,16 @@ T = TypeVar("T", SingleImage, InitArgsModel)
|
|
9
7
|
|
10
8
|
|
11
9
|
def deduplicate_list(
|
12
|
-
this_list: list[T],
|
10
|
+
this_list: list[T],
|
13
11
|
) -> list[T]:
|
14
12
|
"""
|
15
|
-
Custom replacement for `set(this_list)`, when items are
|
16
|
-
instances and then non-hashable (e.g. SingleImage or InitArgsModel).
|
13
|
+
Custom replacement for `set(this_list)`, when items are non-hashable.
|
17
14
|
"""
|
18
|
-
this_list_dict = [this_item.dict() for this_item in this_list]
|
19
15
|
new_list_dict = []
|
20
|
-
|
16
|
+
new_list_objs = []
|
17
|
+
for this_obj in this_list:
|
18
|
+
this_dict = this_obj.dict()
|
21
19
|
if this_dict not in new_list_dict:
|
22
20
|
new_list_dict.append(this_dict)
|
23
|
-
|
24
|
-
return
|
21
|
+
new_list_objs.append(this_obj)
|
22
|
+
return new_list_objs
|
@@ -2,7 +2,6 @@ from copy import copy
|
|
2
2
|
|
3
3
|
from fractal_server.app.runner.v2.deduplicate_list import deduplicate_list
|
4
4
|
from fractal_server.app.runner.v2.task_interface import TaskOutput
|
5
|
-
from fractal_server.images import SingleImage
|
6
5
|
|
7
6
|
|
8
7
|
def merge_outputs(task_outputs: list[TaskOutput]) -> TaskOutput:
|
@@ -24,9 +23,7 @@ def merge_outputs(task_outputs: list[TaskOutput]) -> TaskOutput:
|
|
24
23
|
raise ValueError(f"{current_new_filters=} but {last_new_filters=}")
|
25
24
|
last_new_filters = copy(current_new_filters)
|
26
25
|
|
27
|
-
final_image_list_updates = deduplicate_list(
|
28
|
-
final_image_list_updates, PydanticModel=SingleImage
|
29
|
-
)
|
26
|
+
final_image_list_updates = deduplicate_list(final_image_list_updates)
|
30
27
|
|
31
28
|
additional_args = {}
|
32
29
|
if last_new_filters is not None:
|