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.
Files changed (52) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/models/__init__.py +0 -1
  3. fractal_server/app/models/v1/__init__.py +1 -0
  4. fractal_server/app/routes/admin/v2.py +0 -1
  5. fractal_server/app/routes/api/v1/job.py +3 -3
  6. fractal_server/app/routes/api/v2/__init__.py +2 -2
  7. fractal_server/app/routes/api/v2/dataset.py +0 -1
  8. fractal_server/app/routes/api/v2/images.py +4 -9
  9. fractal_server/app/routes/api/v2/project.py +0 -3
  10. fractal_server/app/routes/api/v2/{apply.py → submit.py} +1 -1
  11. fractal_server/app/routes/api/v2/workflow.py +0 -1
  12. fractal_server/app/runner/executors/slurm/executor.py +23 -10
  13. fractal_server/app/runner/task_files.py +0 -2
  14. fractal_server/app/runner/v1/__init__.py +2 -2
  15. fractal_server/app/runner/v1/_local/__init__.py +1 -1
  16. fractal_server/app/runner/{executors/local → v1/_local}/executor.py +2 -2
  17. fractal_server/app/runner/v2/__init__.py +0 -1
  18. fractal_server/app/runner/v2/_local/__init__.py +1 -3
  19. fractal_server/app/runner/v2/_local/executor.py +100 -0
  20. fractal_server/app/runner/v2/_slurm/__init__.py +68 -86
  21. fractal_server/app/runner/v2/deduplicate_list.py +7 -9
  22. fractal_server/app/runner/v2/merge_outputs.py +1 -4
  23. fractal_server/app/runner/v2/runner.py +19 -16
  24. fractal_server/app/runner/v2/runner_functions.py +14 -12
  25. fractal_server/app/runner/v2/runner_functions_low_level.py +1 -1
  26. fractal_server/app/schemas/v2/dataset.py +2 -1
  27. fractal_server/app/schemas/v2/job.py +2 -1
  28. fractal_server/app/schemas/v2/manifest.py +51 -1
  29. fractal_server/app/schemas/v2/project.py +2 -1
  30. fractal_server/app/schemas/v2/task.py +2 -3
  31. fractal_server/app/schemas/v2/workflow.py +2 -1
  32. fractal_server/app/schemas/v2/workflowtask.py +2 -1
  33. fractal_server/images/__init__.py +2 -50
  34. fractal_server/images/models.py +50 -0
  35. fractal_server/images/tools.py +35 -36
  36. fractal_server/migrations/env.py +0 -2
  37. fractal_server/migrations/versions/{56af171b0159_v2.py → d71e732236cd_v2.py} +29 -7
  38. fractal_server/tasks/v2/background_operations.py +0 -1
  39. {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/METADATA +1 -1
  40. {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/RECORD +44 -50
  41. fractal_server/app/runner/executors/local/__init__.py +0 -3
  42. fractal_server/migrations/versions/4b35c5cefbe3_tmp_is_v2_compatible.py +0 -39
  43. fractal_server/migrations/versions/876f28db9d4e_tmp_split_task_and_wftask_meta.py +0 -68
  44. fractal_server/migrations/versions/974c802f0dd0_tmp_workflowtaskv2_type_in_db.py +0 -37
  45. fractal_server/migrations/versions/9cd305cd6023_tmp_workflowtaskv2.py +0 -40
  46. fractal_server/migrations/versions/a6231ed6273c_tmp_args_schemas_in_taskv2.py +0 -42
  47. fractal_server/migrations/versions/b9e9eed9d442_tmp_taskv2_type.py +0 -37
  48. fractal_server/migrations/versions/e3e639454d4b_tmp_make_task_meta_non_optional.py +0 -50
  49. /fractal_server/app/runner/{v2/components.py → components.py} +0 -0
  50. {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/LICENSE +0 -0
  51. {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/WHEEL +0 -0
  52. {fractal_server-2.0.0a0.dist-info → fractal_server-2.0.0a1.dist-info}/entry_points.txt +0 -0
@@ -1 +1 @@
1
- __VERSION__ = "2.0.0a0"
1
+ __VERSION__ = "2.0.0a1"
@@ -1,7 +1,6 @@
1
1
  """
2
2
  `models` module
3
3
  """
4
- from ..schemas.v1 import * # noqa F401, F403 # FIXME: remove this
5
4
  from .security import * # noqa: F401, F403
6
5
  from .state import State # noqa: F401
7
6
  from .v1 import * # noqa: F401, F403
@@ -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
@@ -268,7 +268,6 @@ async def flag_task_v1_as_v2_compatible(
268
268
  )
269
269
 
270
270
  task.is_v2_compatible = is_v2_compatible
271
- db.add(task)
272
271
  await db.commit()
273
272
  await db.close()
274
273
 
@@ -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(runner_router_v2, tags=["V2 Apply-workflow"])
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"]
@@ -186,7 +186,6 @@ async def delete_dataset(
186
186
  jobs = res.scalars().all()
187
187
  for job in jobs:
188
188
  job.dataset_id = None
189
- await db.merge(job)
190
189
  await db.commit()
191
190
 
192
191
  # Delete dataset
@@ -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 match_filter_SingleImage
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 match_filter_SingleImage(
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 match_filter_SingleImage(
142
- SingleImage(**image),
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
 
@@ -36,7 +36,7 @@ router = APIRouter()
36
36
 
37
37
 
38
38
  @router.post(
39
- "/project/{project_id}/workflow/{workflow_id}/apply/",
39
+ "/project/{project_id}/job/submit/",
40
40
  status_code=status.HTTP_202_ACCEPTED,
41
41
  response_model=JobReadV2,
42
42
  )
@@ -213,7 +213,6 @@ async def delete_workflow(
213
213
  jobs = res.scalars().all()
214
214
  for job in jobs:
215
215
  job.workflow_id = None
216
- await db.merge(job)
217
216
  await db.commit()
218
217
 
219
218
  # Delete workflow
@@ -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
- job.wftask_file_prefixes = tuple(
607
- get_task_file_paths(
608
- workflow_dir=task_files.workflow_dir,
609
- workflow_dir_user=task_files.workflow_dir_user,
610
- task_order=task_files.task_order,
611
- component=component,
612
- ).file_prefix
613
- for component in components
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(
@@ -1,4 +1,3 @@
1
- from functools import lru_cache
2
1
  from pathlib import Path
3
2
  from typing import Optional
4
3
 
@@ -84,7 +83,6 @@ class TaskFiles:
84
83
  )
85
84
 
86
85
 
87
- @lru_cache()
88
86
  def get_task_file_paths(
89
87
  workflow_dir: Path,
90
88
  workflow_dir_user: Path,
@@ -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 ..v1._local import process_workflow as local_process_workflow
38
- from ..v1._slurm import process_workflow as slurm_process_workflow
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 ...v1._local._local_config import get_default_local_backend_config
22
- from ...v1._local._local_config import LocalBackendConfig
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 fractal_server.app.models.v2 import WorkflowV2
25
-
26
- # from ...executors.slurm.executor import FractalSlurmExecutor
27
- # from ._submit_setup import _slurm_submit_setup
28
- # from .get_slurm_config import get_slurm_config
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
- input_paths: list[Path],
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 output dataset metadata.
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
- output_dataset_metadata: Metadata of the output dataset
62
+ new_dataset_attributes:
64
63
  """
65
64
 
66
- raise NotImplementedError
67
-
68
- # if not slurm_user:
69
- # raise RuntimeError(
70
- # "slurm_user argument is required, for slurm backend"
71
- # )
72
-
73
- # if isinstance(worker_init, str):
74
- # worker_init = worker_init.split("\n")
75
-
76
- # with FractalSlurmExecutor(
77
- # debug=True,
78
- # keep_logs=True,
79
- # slurm_user=slurm_user,
80
- # user_cache_dir=user_cache_dir,
81
- # working_dir=workflow_dir,
82
- # working_dir_user=workflow_dir_user,
83
- # common_script_lines=worker_init,
84
- # slurm_account=slurm_account,
85
- # ) as executor:
86
- # output_task_pars = execute_tasks(
87
- # executor=executor,
88
- # task_list=workflow.task_list[
89
- # first_task_index : (last_task_index + 1) # noqa
90
- # ], # noqa
91
- # task_pars=TaskParameters(
92
- # input_paths=input_paths,
93
- # output_path=output_path,
94
- # metadata=input_metadata,
95
- # history=input_history,
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
- input_paths: list[Path],
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
- first_task_index: Optional[int] = None,
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
- raise NotImplementedError
132
-
133
- # # Set values of first_task_index and last_task_index
134
- # num_tasks = len(workflow.task_list)
135
- # first_task_index, last_task_index = set_start_and_last_task_index(
136
- # num_tasks,
137
- # first_task_index=first_task_index,
138
- # last_task_index=last_task_index,
139
- # )
140
-
141
- # output_dataset_metadata_history = await async_wrap(_process_workflow)(
142
- # workflow=workflow,
143
- # input_paths=input_paths,
144
- # output_path=output_path,
145
- # input_metadata=input_metadata,
146
- # input_history=input_history,
147
- # logger_name=logger_name,
148
- # workflow_dir=workflow_dir,
149
- # workflow_dir_user=workflow_dir_user,
150
- # slurm_user=slurm_user,
151
- # slurm_account=slurm_account,
152
- # user_cache_dir=user_cache_dir,
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], PydanticModel: ModelMetaclass
10
+ this_list: list[T],
13
11
  ) -> list[T]:
14
12
  """
15
- Custom replacement for `set(this_list)`, when items are Pydantic-model
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
- for this_dict in this_list_dict:
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
- new_list = [PydanticModel(**this_dict) for this_dict in new_list_dict]
24
- return new_list
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: