fractal-server 1.4.9__py3-none-any.whl → 2.0.0a0__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 (132) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/models/__init__.py +4 -7
  3. fractal_server/app/models/linkuserproject.py +9 -0
  4. fractal_server/app/models/security.py +6 -0
  5. fractal_server/app/models/state.py +1 -1
  6. fractal_server/app/models/v1/__init__.py +10 -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/{task.py → v1/task.py} +7 -2
  11. fractal_server/app/models/{workflow.py → v1/workflow.py} +5 -5
  12. fractal_server/app/models/v2/__init__.py +20 -0
  13. fractal_server/app/models/v2/dataset.py +55 -0
  14. fractal_server/app/models/v2/job.py +51 -0
  15. fractal_server/app/models/v2/project.py +31 -0
  16. fractal_server/app/models/v2/task.py +93 -0
  17. fractal_server/app/models/v2/workflow.py +43 -0
  18. fractal_server/app/models/v2/workflowtask.py +90 -0
  19. fractal_server/app/routes/{admin.py → admin/v1.py} +42 -42
  20. fractal_server/app/routes/admin/v2.py +275 -0
  21. fractal_server/app/routes/api/v1/__init__.py +7 -7
  22. fractal_server/app/routes/api/v1/_aux_functions.py +2 -2
  23. fractal_server/app/routes/api/v1/dataset.py +44 -37
  24. fractal_server/app/routes/api/v1/job.py +12 -12
  25. fractal_server/app/routes/api/v1/project.py +23 -21
  26. fractal_server/app/routes/api/v1/task.py +24 -14
  27. fractal_server/app/routes/api/v1/task_collection.py +16 -14
  28. fractal_server/app/routes/api/v1/workflow.py +24 -24
  29. fractal_server/app/routes/api/v1/workflowtask.py +10 -10
  30. fractal_server/app/routes/api/v2/__init__.py +28 -0
  31. fractal_server/app/routes/api/v2/_aux_functions.py +497 -0
  32. fractal_server/app/routes/api/v2/apply.py +220 -0
  33. fractal_server/app/routes/api/v2/dataset.py +310 -0
  34. fractal_server/app/routes/api/v2/images.py +212 -0
  35. fractal_server/app/routes/api/v2/job.py +200 -0
  36. fractal_server/app/routes/api/v2/project.py +205 -0
  37. fractal_server/app/routes/api/v2/task.py +222 -0
  38. fractal_server/app/routes/api/v2/task_collection.py +229 -0
  39. fractal_server/app/routes/api/v2/workflow.py +398 -0
  40. fractal_server/app/routes/api/v2/workflowtask.py +269 -0
  41. fractal_server/app/routes/aux/_job.py +1 -1
  42. fractal_server/app/runner/async_wrap.py +27 -0
  43. fractal_server/app/runner/exceptions.py +129 -0
  44. fractal_server/app/runner/executors/local/__init__.py +3 -0
  45. fractal_server/app/runner/{_local → executors/local}/executor.py +2 -2
  46. fractal_server/app/runner/executors/slurm/__init__.py +3 -0
  47. fractal_server/app/runner/{_slurm → executors/slurm}/_batching.py +1 -1
  48. fractal_server/app/runner/executors/slurm/_check_jobs_status.py +72 -0
  49. fractal_server/app/runner/{_slurm → executors/slurm}/_executor_wait_thread.py +3 -4
  50. fractal_server/app/runner/{_slurm → executors/slurm}/_slurm_config.py +3 -152
  51. fractal_server/app/runner/{_slurm → executors/slurm}/_subprocess_run_as_user.py +1 -1
  52. fractal_server/app/runner/{_slurm → executors/slurm}/executor.py +9 -9
  53. fractal_server/app/runner/filenames.py +6 -0
  54. fractal_server/app/runner/set_start_and_last_task_index.py +39 -0
  55. fractal_server/app/runner/task_files.py +105 -0
  56. fractal_server/app/runner/{__init__.py → v1/__init__.py} +36 -49
  57. fractal_server/app/runner/{_common.py → v1/_common.py} +13 -120
  58. fractal_server/app/runner/{_local → v1/_local}/__init__.py +6 -6
  59. fractal_server/app/runner/{_local → v1/_local}/_local_config.py +6 -7
  60. fractal_server/app/runner/{_local → v1/_local}/_submit_setup.py +1 -5
  61. fractal_server/app/runner/v1/_slurm/__init__.py +310 -0
  62. fractal_server/app/runner/{_slurm → v1/_slurm}/_submit_setup.py +3 -9
  63. fractal_server/app/runner/v1/_slurm/get_slurm_config.py +163 -0
  64. fractal_server/app/runner/v1/common.py +117 -0
  65. fractal_server/app/runner/{handle_failed_job.py → v1/handle_failed_job.py} +8 -8
  66. fractal_server/app/runner/v2/__init__.py +337 -0
  67. fractal_server/app/runner/v2/_local/__init__.py +169 -0
  68. fractal_server/app/runner/v2/_local/_local_config.py +118 -0
  69. fractal_server/app/runner/v2/_local/_submit_setup.py +52 -0
  70. fractal_server/app/runner/v2/_slurm/__init__.py +157 -0
  71. fractal_server/app/runner/v2/_slurm/_submit_setup.py +83 -0
  72. fractal_server/app/runner/v2/_slurm/get_slurm_config.py +179 -0
  73. fractal_server/app/runner/v2/components.py +5 -0
  74. fractal_server/app/runner/v2/deduplicate_list.py +24 -0
  75. fractal_server/app/runner/v2/handle_failed_job.py +156 -0
  76. fractal_server/app/runner/v2/merge_outputs.py +41 -0
  77. fractal_server/app/runner/v2/runner.py +264 -0
  78. fractal_server/app/runner/v2/runner_functions.py +339 -0
  79. fractal_server/app/runner/v2/runner_functions_low_level.py +134 -0
  80. fractal_server/app/runner/v2/task_interface.py +43 -0
  81. fractal_server/app/runner/v2/v1_compat.py +21 -0
  82. fractal_server/app/schemas/__init__.py +4 -42
  83. fractal_server/app/schemas/v1/__init__.py +42 -0
  84. fractal_server/app/schemas/{applyworkflow.py → v1/applyworkflow.py} +18 -18
  85. fractal_server/app/schemas/{dataset.py → v1/dataset.py} +30 -30
  86. fractal_server/app/schemas/{dumps.py → v1/dumps.py} +8 -8
  87. fractal_server/app/schemas/{manifest.py → v1/manifest.py} +5 -5
  88. fractal_server/app/schemas/{project.py → v1/project.py} +9 -9
  89. fractal_server/app/schemas/{task.py → v1/task.py} +12 -12
  90. fractal_server/app/schemas/{task_collection.py → v1/task_collection.py} +7 -7
  91. fractal_server/app/schemas/{workflow.py → v1/workflow.py} +38 -38
  92. fractal_server/app/schemas/v2/__init__.py +34 -0
  93. fractal_server/app/schemas/v2/dataset.py +88 -0
  94. fractal_server/app/schemas/v2/dumps.py +87 -0
  95. fractal_server/app/schemas/v2/job.py +113 -0
  96. fractal_server/app/schemas/v2/manifest.py +109 -0
  97. fractal_server/app/schemas/v2/project.py +36 -0
  98. fractal_server/app/schemas/v2/task.py +121 -0
  99. fractal_server/app/schemas/v2/task_collection.py +105 -0
  100. fractal_server/app/schemas/v2/workflow.py +78 -0
  101. fractal_server/app/schemas/v2/workflowtask.py +118 -0
  102. fractal_server/config.py +5 -10
  103. fractal_server/images/__init__.py +50 -0
  104. fractal_server/images/tools.py +86 -0
  105. fractal_server/main.py +11 -3
  106. fractal_server/migrations/versions/4b35c5cefbe3_tmp_is_v2_compatible.py +39 -0
  107. fractal_server/migrations/versions/56af171b0159_v2.py +217 -0
  108. fractal_server/migrations/versions/876f28db9d4e_tmp_split_task_and_wftask_meta.py +68 -0
  109. fractal_server/migrations/versions/974c802f0dd0_tmp_workflowtaskv2_type_in_db.py +37 -0
  110. fractal_server/migrations/versions/9cd305cd6023_tmp_workflowtaskv2.py +40 -0
  111. fractal_server/migrations/versions/a6231ed6273c_tmp_args_schemas_in_taskv2.py +42 -0
  112. fractal_server/migrations/versions/b9e9eed9d442_tmp_taskv2_type.py +37 -0
  113. fractal_server/migrations/versions/e3e639454d4b_tmp_make_task_meta_non_optional.py +50 -0
  114. fractal_server/tasks/__init__.py +0 -5
  115. fractal_server/tasks/endpoint_operations.py +13 -19
  116. fractal_server/tasks/utils.py +35 -0
  117. fractal_server/tasks/{_TaskCollectPip.py → v1/_TaskCollectPip.py} +3 -3
  118. fractal_server/tasks/{background_operations.py → v1/background_operations.py} +18 -50
  119. fractal_server/tasks/v1/get_collection_data.py +14 -0
  120. fractal_server/tasks/v2/_TaskCollectPip.py +103 -0
  121. fractal_server/tasks/v2/background_operations.py +382 -0
  122. fractal_server/tasks/v2/get_collection_data.py +14 -0
  123. {fractal_server-1.4.9.dist-info → fractal_server-2.0.0a0.dist-info}/METADATA +3 -4
  124. fractal_server-2.0.0a0.dist-info/RECORD +166 -0
  125. fractal_server/app/runner/_slurm/.gitignore +0 -2
  126. fractal_server/app/runner/_slurm/__init__.py +0 -150
  127. fractal_server/app/runner/common.py +0 -311
  128. fractal_server-1.4.9.dist-info/RECORD +0 -97
  129. /fractal_server/app/runner/{_slurm → executors/slurm}/remote.py +0 -0
  130. {fractal_server-1.4.9.dist-info → fractal_server-2.0.0a0.dist-info}/LICENSE +0 -0
  131. {fractal_server-1.4.9.dist-info → fractal_server-2.0.0a0.dist-info}/WHEEL +0 -0
  132. {fractal_server-1.4.9.dist-info → fractal_server-2.0.0a0.dist-info}/entry_points.txt +0 -0
@@ -11,7 +11,6 @@ import subprocess # nosec
11
11
  import traceback
12
12
  from concurrent.futures import Executor
13
13
  from copy import deepcopy
14
- from functools import lru_cache
15
14
  from functools import partial
16
15
  from pathlib import Path
17
16
  from shlex import split as shlex_split
@@ -19,22 +18,19 @@ from typing import Any
19
18
  from typing import Callable
20
19
  from typing import Optional
21
20
 
22
- from ...config import get_settings
23
- from ...logger import get_logger
24
- from ...syringe import Inject
25
- from ..models import Task
26
- from ..models import WorkflowTask
27
- from ..schemas import WorkflowTaskStatusType
28
- from .common import JobExecutionError
29
- from .common import TaskExecutionError
21
+ from ....config import get_settings
22
+ from ....logger import get_logger
23
+ from ....syringe import Inject
24
+ from ...models import Task
25
+ from ...models import WorkflowTask
26
+ from ...schemas.v1 import WorkflowTaskStatusTypeV1
27
+ from ..exceptions import JobExecutionError
28
+ from ..exceptions import TaskExecutionError
30
29
  from .common import TaskParameters
31
30
  from .common import write_args_file
32
-
33
-
34
- HISTORY_FILENAME = "history.json"
35
- METADATA_FILENAME = "metadata.json"
36
- SHUTDOWN_FILENAME = "shutdown"
37
- WORKFLOW_LOG_FILENAME = "workflow.log"
31
+ from fractal_server.app.runner.filenames import HISTORY_FILENAME
32
+ from fractal_server.app.runner.filenames import METADATA_FILENAME
33
+ from fractal_server.app.runner.task_files import get_task_file_paths
38
34
 
39
35
 
40
36
  def no_op_submit_setup_call(
@@ -42,7 +38,6 @@ def no_op_submit_setup_call(
42
38
  wftask: WorkflowTask,
43
39
  workflow_dir: Path,
44
40
  workflow_dir_user: Path,
45
- task_pars: TaskParameters,
46
41
  ) -> dict:
47
42
  """
48
43
  Default (no-operation) interface of submit_setup_call.
@@ -50,14 +45,6 @@ def no_op_submit_setup_call(
50
45
  return {}
51
46
 
52
47
 
53
- def sanitize_component(value: str) -> str:
54
- """
55
- Remove {" ", "/", "."} form a string, e.g. going from
56
- 'plate.zarr/B/03/0' to 'plate_zarr_B_03_0'.
57
- """
58
- return value.replace(" ", "_").replace("/", "_").replace(".", "_")
59
-
60
-
61
48
  def _task_needs_image_list(_task: Task) -> bool:
62
49
  """
63
50
  Whether a task requires `metadata["image"]` in its `args.json` file.
@@ -78,98 +65,6 @@ def _task_needs_image_list(_task: Task) -> bool:
78
65
  return False
79
66
 
80
67
 
81
- class TaskFiles:
82
- """
83
- Group all file paths pertaining to a task
84
-
85
- Attributes:
86
- workflow_dir:
87
- Server-owned directory to store all task-execution-related relevant
88
- files (inputs, outputs, errors, and all meta files related to the
89
- job execution). Note: users cannot write directly to this folder.
90
- workflow_dir_user:
91
- User-side directory with the same scope as `workflow_dir`, and
92
- where a user can write.
93
- task_order:
94
- Positional order of the task within a workflow.
95
- component:
96
- Specific component to run the task for (relevant for tasks that
97
- will be executed in parallel over many components).
98
- file_prefix:
99
- Prefix for all task-related files.
100
- args:
101
- Path for input json file.
102
- metadiff:
103
- Path for output json file with metadata update.
104
- out:
105
- Path for task-execution stdout.
106
- err:
107
- Path for task-execution stderr.
108
- """
109
-
110
- workflow_dir: Path
111
- workflow_dir_user: Path
112
- task_order: Optional[int] = None
113
- component: Optional[str] = None
114
-
115
- file_prefix: str
116
- args: Path
117
- out: Path
118
- err: Path
119
- metadiff: Path
120
-
121
- def __init__(
122
- self,
123
- workflow_dir: Path,
124
- workflow_dir_user: Path,
125
- task_order: Optional[int] = None,
126
- component: Optional[str] = None,
127
- ):
128
- self.workflow_dir = workflow_dir
129
- self.workflow_dir_user = workflow_dir_user
130
- self.task_order = task_order
131
- self.component = component
132
-
133
- if self.component is not None:
134
- component_safe = sanitize_component(str(self.component))
135
- component_safe = f"_par_{component_safe}"
136
- else:
137
- component_safe = ""
138
-
139
- if self.task_order is not None:
140
- order = str(self.task_order)
141
- else:
142
- order = "task"
143
- self.file_prefix = f"{order}{component_safe}"
144
- self.args = self.workflow_dir_user / f"{self.file_prefix}.args.json"
145
- self.out = self.workflow_dir_user / f"{self.file_prefix}.out"
146
- self.err = self.workflow_dir_user / f"{self.file_prefix}.err"
147
- self.metadiff = (
148
- self.workflow_dir_user / f"{self.file_prefix}.metadiff.json"
149
- )
150
-
151
-
152
- @lru_cache()
153
- def get_task_file_paths(
154
- workflow_dir: Path,
155
- workflow_dir_user: Path,
156
- task_order: Optional[int] = None,
157
- component: Optional[str] = None,
158
- ) -> TaskFiles:
159
- """
160
- Return the corrisponding TaskFiles object
161
-
162
- This function is mainly used as a cache to avoid instantiating needless
163
- objects.
164
- """
165
- return TaskFiles(
166
- workflow_dir=workflow_dir,
167
- workflow_dir_user=workflow_dir_user,
168
- task_order=task_order,
169
- component=component,
170
- )
171
-
172
-
173
68
  def _call_command_wrapper(cmd: str, stdout: Path, stderr: Path) -> None:
174
69
  """
175
70
  Call a command and write its stdout and stderr to files
@@ -331,7 +226,7 @@ def call_single_task(
331
226
  wftask_dump["task"] = wftask.task.model_dump()
332
227
  new_history_item = dict(
333
228
  workflowtask=wftask_dump,
334
- status=WorkflowTaskStatusType.DONE,
229
+ status=WorkflowTaskStatusTypeV1.DONE,
335
230
  parallelization=None,
336
231
  )
337
232
  updated_history = task_pars.history.copy()
@@ -529,7 +424,6 @@ def call_parallel_task(
529
424
  try:
530
425
  extra_setup = submit_setup_call(
531
426
  wftask=wftask,
532
- task_pars=task_pars_depend,
533
427
  workflow_dir=workflow_dir,
534
428
  workflow_dir_user=workflow_dir_user,
535
429
  )
@@ -592,7 +486,7 @@ def call_parallel_task(
592
486
  wftask_dump["task"] = wftask.task.model_dump()
593
487
  new_history_item = dict(
594
488
  workflowtask=wftask_dump,
595
- status=WorkflowTaskStatusType.DONE,
489
+ status=WorkflowTaskStatusTypeV1.DONE,
596
490
  parallelization=dict(
597
491
  parallelization_level=wftask.parallelization_level,
598
492
  component_list=component_list,
@@ -681,7 +575,6 @@ def execute_tasks(
681
575
  try:
682
576
  extra_setup = submit_setup_call(
683
577
  wftask=this_wftask,
684
- task_pars=current_task_pars,
685
578
  workflow_dir=workflow_dir,
686
579
  workflow_dir_user=workflow_dir_user,
687
580
  )
@@ -23,13 +23,13 @@ from pathlib import Path
23
23
  from typing import Any
24
24
  from typing import Optional
25
25
 
26
- from ...models import Workflow
27
- from .._common import execute_tasks
28
- from ..common import async_wrap
29
- from ..common import set_start_and_last_task_index
30
- from ..common import TaskParameters
26
+ from ....models import Workflow # FIXME: this is v1 specific
27
+ from ...async_wrap import async_wrap
28
+ from ...executors.local.executor import FractalThreadPoolExecutor
29
+ from ...set_start_and_last_task_index import set_start_and_last_task_index
30
+ from .._common import execute_tasks # FIXME: this is v1 specific
31
+ from ..common import TaskParameters # FIXME: this is v1 specific
31
32
  from ._submit_setup import _local_submit_setup
32
- from .executor import FractalThreadPoolExecutor
33
33
 
34
34
 
35
35
  def _process_workflow(
@@ -19,9 +19,9 @@ from pydantic import BaseModel
19
19
  from pydantic import Extra
20
20
  from pydantic.error_wrappers import ValidationError
21
21
 
22
- from ....config import get_settings
23
- from ....syringe import Inject
24
- from ...models import WorkflowTask
22
+ from .....config import get_settings
23
+ from .....syringe import Inject
24
+ from ....models.v1 import WorkflowTask
25
25
 
26
26
 
27
27
  class LocalBackendConfigError(ValueError):
@@ -63,15 +63,14 @@ def get_local_backend_config(
63
63
  The sources for `parallel_tasks_per_job` attributes, starting from the
64
64
  highest-priority one, are
65
65
 
66
- 1. Properties in `wftask.meta` (which, for `WorkflowTask`s added through
67
- `Workflow.insert_task`, also includes `wftask.task.meta`);
66
+ 1. Properties in `wftask.meta`;
68
67
  2. The general content of the local-backend configuration file;
69
68
  3. The default value (`None`).
70
69
 
71
70
  Arguments:
72
71
  wftask:
73
- WorkflowTask for which the backend configuration is is to be
74
- prepared.
72
+ WorkflowTask (V1) for which the backend configuration should
73
+ be prepared.
75
74
  config_path:
76
75
  Path of local-backend configuration file; if `None`, use
77
76
  `FRACTAL_LOCAL_CONFIG_FILE` variable from settings.
@@ -14,8 +14,7 @@ Submodule to define _local_submit_setup
14
14
  from pathlib import Path
15
15
  from typing import Optional
16
16
 
17
- from ...models import WorkflowTask
18
- from ..common import TaskParameters
17
+ from ....models.v1 import WorkflowTask
19
18
  from ._local_config import get_local_backend_config
20
19
 
21
20
 
@@ -24,7 +23,6 @@ def _local_submit_setup(
24
23
  wftask: WorkflowTask,
25
24
  workflow_dir: Optional[Path] = None,
26
25
  workflow_dir_user: Optional[Path] = None,
27
- task_pars: Optional[TaskParameters] = None,
28
26
  ) -> dict[str, object]:
29
27
  """
30
28
  Collect WorfklowTask-specific configuration parameters from different
@@ -33,8 +31,6 @@ def _local_submit_setup(
33
31
  Arguments:
34
32
  wftask:
35
33
  WorkflowTask for which the configuration is to be assembled
36
- task_pars:
37
- Not used in this function.
38
34
  workflow_dir:
39
35
  Not used in this function.
40
36
  workflow_dir_user:
@@ -0,0 +1,310 @@
1
+ # Copyright 2022 (C) Friedrich Miescher Institute for Biomedical Research and
2
+ # University of Zurich
3
+ #
4
+ # Original authors:
5
+ # Jacopo Nespolo <jacopo.nespolo@exact-lab.it>
6
+ # Tommaso Comparin <tommaso.comparin@exact-lab.it>
7
+ # Marco Franzon <marco.franzon@exact-lab.it>
8
+ #
9
+ # This file is part of Fractal and was originally developed by eXact lab S.r.l.
10
+ # <exact-lab.it> under contract with Liberali Lab from the Friedrich Miescher
11
+ # Institute for Biomedical Research and Pelkmans Lab from the University of
12
+ # Zurich.
13
+ """
14
+ Slurm Bakend
15
+
16
+ This backend runs fractal workflows in a SLURM cluster using Clusterfutures
17
+ Executor objects.
18
+ """
19
+ from pathlib import Path
20
+ from typing import Any
21
+ from typing import Optional
22
+ from typing import Union
23
+
24
+ from ...async_wrap import async_wrap
25
+ from ...executors.slurm.executor import FractalSlurmExecutor
26
+ from ...set_start_and_last_task_index import set_start_and_last_task_index
27
+ from .._common import execute_tasks
28
+ from ..common import TaskParameters
29
+ from ._submit_setup import _slurm_submit_setup
30
+ from fractal_server.app.models.v1 import Workflow
31
+ from fractal_server.app.models.v1 import WorkflowTask
32
+ from fractal_server.app.runner.executors.slurm._slurm_config import (
33
+ _parse_mem_value,
34
+ )
35
+ from fractal_server.app.runner.executors.slurm._slurm_config import (
36
+ load_slurm_config_file,
37
+ )
38
+ from fractal_server.app.runner.executors.slurm._slurm_config import logger
39
+ from fractal_server.app.runner.executors.slurm._slurm_config import SlurmConfig
40
+ from fractal_server.app.runner.executors.slurm._slurm_config import (
41
+ SlurmConfigError,
42
+ )
43
+
44
+
45
+ def _process_workflow(
46
+ *,
47
+ workflow: Workflow,
48
+ input_paths: list[Path],
49
+ output_path: Path,
50
+ input_metadata: dict[str, Any],
51
+ input_history: list[dict[str, Any]],
52
+ logger_name: str,
53
+ workflow_dir: Path,
54
+ workflow_dir_user: Path,
55
+ first_task_index: int,
56
+ last_task_index: int,
57
+ slurm_user: Optional[str] = None,
58
+ slurm_account: Optional[str] = None,
59
+ user_cache_dir: str,
60
+ worker_init: Optional[Union[str, list[str]]] = None,
61
+ ) -> dict[str, Any]:
62
+ """
63
+ Internal processing routine for the SLURM backend
64
+
65
+ This function initialises the a FractalSlurmExecutor, setting logging,
66
+ workflow working dir and user to impersonate. It then schedules the
67
+ workflow tasks and returns the output dataset metadata.
68
+
69
+ Cf. [process_workflow][fractal_server.app.runner._local.process_workflow]
70
+
71
+ Returns:
72
+ output_dataset_metadata: Metadata of the output dataset
73
+ """
74
+
75
+ if not slurm_user:
76
+ raise RuntimeError(
77
+ "slurm_user argument is required, for slurm backend"
78
+ )
79
+
80
+ if isinstance(worker_init, str):
81
+ worker_init = worker_init.split("\n")
82
+
83
+ with FractalSlurmExecutor(
84
+ debug=True,
85
+ keep_logs=True,
86
+ slurm_user=slurm_user,
87
+ user_cache_dir=user_cache_dir,
88
+ working_dir=workflow_dir,
89
+ working_dir_user=workflow_dir_user,
90
+ common_script_lines=worker_init,
91
+ slurm_account=slurm_account,
92
+ ) as executor:
93
+ output_task_pars = execute_tasks(
94
+ executor=executor,
95
+ task_list=workflow.task_list[
96
+ first_task_index : (last_task_index + 1) # noqa
97
+ ], # noqa
98
+ task_pars=TaskParameters(
99
+ input_paths=input_paths,
100
+ output_path=output_path,
101
+ metadata=input_metadata,
102
+ history=input_history,
103
+ ),
104
+ workflow_dir=workflow_dir,
105
+ workflow_dir_user=workflow_dir_user,
106
+ submit_setup_call=_slurm_submit_setup,
107
+ logger_name=logger_name,
108
+ )
109
+ output_dataset_metadata_history = dict(
110
+ metadata=output_task_pars.metadata, history=output_task_pars.history
111
+ )
112
+ return output_dataset_metadata_history
113
+
114
+
115
+ async def process_workflow(
116
+ *,
117
+ workflow: Workflow,
118
+ input_paths: list[Path],
119
+ output_path: Path,
120
+ input_metadata: dict[str, Any],
121
+ input_history: list[dict[str, Any]],
122
+ logger_name: str,
123
+ workflow_dir: Path,
124
+ workflow_dir_user: Optional[Path] = None,
125
+ user_cache_dir: Optional[str] = None,
126
+ slurm_user: Optional[str] = None,
127
+ slurm_account: Optional[str] = None,
128
+ worker_init: Optional[str] = None,
129
+ first_task_index: Optional[int] = None,
130
+ last_task_index: Optional[int] = None,
131
+ ) -> dict[str, Any]:
132
+ """
133
+ Process workflow (SLURM backend public interface)
134
+
135
+ Cf. [process_workflow][fractal_server.app.runner._local.process_workflow]
136
+ """
137
+
138
+ # Set values of first_task_index and last_task_index
139
+ num_tasks = len(workflow.task_list)
140
+ first_task_index, last_task_index = set_start_and_last_task_index(
141
+ num_tasks,
142
+ first_task_index=first_task_index,
143
+ last_task_index=last_task_index,
144
+ )
145
+
146
+ output_dataset_metadata_history = await async_wrap(_process_workflow)(
147
+ workflow=workflow,
148
+ input_paths=input_paths,
149
+ output_path=output_path,
150
+ input_metadata=input_metadata,
151
+ input_history=input_history,
152
+ logger_name=logger_name,
153
+ workflow_dir=workflow_dir,
154
+ workflow_dir_user=workflow_dir_user,
155
+ slurm_user=slurm_user,
156
+ slurm_account=slurm_account,
157
+ user_cache_dir=user_cache_dir,
158
+ worker_init=worker_init,
159
+ first_task_index=first_task_index,
160
+ last_task_index=last_task_index,
161
+ )
162
+ return output_dataset_metadata_history
163
+
164
+
165
+ def get_slurm_config(
166
+ wftask: WorkflowTask,
167
+ workflow_dir: Path,
168
+ workflow_dir_user: Path,
169
+ config_path: Optional[Path] = None,
170
+ ) -> SlurmConfig:
171
+ """
172
+ Prepare a `SlurmConfig` configuration object
173
+
174
+ The sources for `SlurmConfig` attributes, in increasing priority order, are
175
+
176
+ 1. The general content of the Fractal SLURM configuration file.
177
+ 2. The GPU-specific content of the Fractal SLURM configuration file, if
178
+ appropriate.
179
+ 3. Properties in `wftask.meta` (which, for `WorkflowTask`s added through
180
+ `Workflow.insert_task`, also includes `wftask.task.meta`);
181
+
182
+ Note: `wftask.meta` may be `None`.
183
+
184
+ Arguments:
185
+ wftask:
186
+ WorkflowTask for which the SLURM configuration is is to be
187
+ prepared.
188
+ workflow_dir:
189
+ Server-owned directory to store all task-execution-related relevant
190
+ files (inputs, outputs, errors, and all meta files related to the
191
+ job execution). Note: users cannot write directly to this folder.
192
+ workflow_dir_user:
193
+ User-side directory with the same scope as `workflow_dir`, and
194
+ where a user can write.
195
+ config_path:
196
+ Path of aFractal SLURM configuration file; if `None`, use
197
+ `FRACTAL_SLURM_CONFIG_FILE` variable from settings.
198
+
199
+ Returns:
200
+ slurm_config:
201
+ The SlurmConfig object
202
+ """
203
+
204
+ logger.debug(
205
+ "[get_slurm_config] WorkflowTask meta attribute: {wftask.meta=}"
206
+ )
207
+
208
+ # Incorporate slurm_env.default_slurm_config
209
+ slurm_env = load_slurm_config_file(config_path=config_path)
210
+ slurm_dict = slurm_env.default_slurm_config.dict(
211
+ exclude_unset=True, exclude={"mem"}
212
+ )
213
+ if slurm_env.default_slurm_config.mem:
214
+ slurm_dict["mem_per_task_MB"] = slurm_env.default_slurm_config.mem
215
+
216
+ # Incorporate slurm_env.batching_config
217
+ for key, value in slurm_env.batching_config.dict().items():
218
+ slurm_dict[key] = value
219
+
220
+ # Incorporate slurm_env.user_local_exports
221
+ slurm_dict["user_local_exports"] = slurm_env.user_local_exports
222
+
223
+ logger.debug(
224
+ "[get_slurm_config] Fractal SLURM configuration file: "
225
+ f"{slurm_env.dict()=}"
226
+ )
227
+
228
+ # GPU-related options
229
+ # Notes about priority:
230
+ # 1. This block of definitions takes priority over other definitions from
231
+ # slurm_env which are not under the `needs_gpu` subgroup
232
+ # 2. This block of definitions has lower priority than whatever comes next
233
+ # (i.e. from WorkflowTask.meta).
234
+ if wftask.meta is not None:
235
+ needs_gpu = wftask.meta.get("needs_gpu", False)
236
+ else:
237
+ needs_gpu = False
238
+ logger.debug(f"[get_slurm_config] {needs_gpu=}")
239
+ if needs_gpu:
240
+ for key, value in slurm_env.gpu_slurm_config.dict(
241
+ exclude_unset=True, exclude={"mem"}
242
+ ).items():
243
+ slurm_dict[key] = value
244
+ if slurm_env.gpu_slurm_config.mem:
245
+ slurm_dict["mem_per_task_MB"] = slurm_env.gpu_slurm_config.mem
246
+
247
+ # Number of CPUs per task, for multithreading
248
+ if wftask.meta is not None and "cpus_per_task" in wftask.meta:
249
+ cpus_per_task = int(wftask.meta["cpus_per_task"])
250
+ slurm_dict["cpus_per_task"] = cpus_per_task
251
+
252
+ # Required memory per task, in MB
253
+ if wftask.meta is not None and "mem" in wftask.meta:
254
+ raw_mem = wftask.meta["mem"]
255
+ mem_per_task_MB = _parse_mem_value(raw_mem)
256
+ slurm_dict["mem_per_task_MB"] = mem_per_task_MB
257
+
258
+ # Job name
259
+ job_name = wftask.task.name.replace(" ", "_")
260
+ slurm_dict["job_name"] = job_name
261
+
262
+ # Optional SLURM arguments and extra lines
263
+ if wftask.meta is not None:
264
+ account = wftask.meta.get("account", None)
265
+ if account is not None:
266
+ error_msg = (
267
+ f"Invalid {account=} property in WorkflowTask `meta` "
268
+ "attribute.\n"
269
+ "SLURM account must be set in the request body of the "
270
+ "apply-workflow endpoint, or by modifying the user properties."
271
+ )
272
+ logger.error(error_msg)
273
+ raise SlurmConfigError(error_msg)
274
+ for key in ["time", "gres", "constraint"]:
275
+ value = wftask.meta.get(key, None)
276
+ if value:
277
+ slurm_dict[key] = value
278
+ if wftask.meta is not None:
279
+ extra_lines = wftask.meta.get("extra_lines", [])
280
+ else:
281
+ extra_lines = []
282
+ extra_lines = slurm_dict.get("extra_lines", []) + extra_lines
283
+ if len(set(extra_lines)) != len(extra_lines):
284
+ logger.debug(
285
+ "[get_slurm_config] Removing repeated elements "
286
+ f"from {extra_lines=}."
287
+ )
288
+ extra_lines = list(set(extra_lines))
289
+ slurm_dict["extra_lines"] = extra_lines
290
+
291
+ # Job-batching parameters (if None, they will be determined heuristically)
292
+ if wftask.meta is not None:
293
+ tasks_per_job = wftask.meta.get("tasks_per_job", None)
294
+ parallel_tasks_per_job = wftask.meta.get(
295
+ "parallel_tasks_per_job", None
296
+ )
297
+ else:
298
+ tasks_per_job = None
299
+ parallel_tasks_per_job = None
300
+ slurm_dict["tasks_per_job"] = tasks_per_job
301
+ slurm_dict["parallel_tasks_per_job"] = parallel_tasks_per_job
302
+
303
+ # Put everything together
304
+ logger.debug(
305
+ "[get_slurm_config] Now create a SlurmConfig object based "
306
+ f"on {slurm_dict=}"
307
+ )
308
+ slurm_config = SlurmConfig(**slurm_dict)
309
+
310
+ return slurm_config
@@ -15,12 +15,10 @@ implementation of `submit_setup_call` in
15
15
  [fractal_server.app.runner._common][]).
16
16
  """
17
17
  from pathlib import Path
18
- from typing import Optional
19
18
 
20
- from ...models import WorkflowTask
21
- from .._common import get_task_file_paths
22
- from ..common import TaskParameters
23
- from ._slurm_config import get_slurm_config
19
+ from ...task_files import get_task_file_paths
20
+ from .get_slurm_config import get_slurm_config
21
+ from fractal_server.app.models.v1 import WorkflowTask
24
22
 
25
23
 
26
24
  def _slurm_submit_setup(
@@ -28,7 +26,6 @@ def _slurm_submit_setup(
28
26
  wftask: WorkflowTask,
29
27
  workflow_dir: Path,
30
28
  workflow_dir_user: Path,
31
- task_pars: Optional[TaskParameters] = None,
32
29
  ) -> dict[str, object]:
33
30
  """
34
31
  Collect WorfklowTask-specific configuration parameters from different
@@ -46,9 +43,6 @@ def _slurm_submit_setup(
46
43
  Arguments:
47
44
  wftask:
48
45
  WorkflowTask for which the configuration is to be assembled
49
- task_pars:
50
- Task parameters to be passed to the task
51
- (not used in this function)
52
46
  workflow_dir:
53
47
  Server-owned directory to store all task-execution-related relevant
54
48
  files (inputs, outputs, errors, and all meta files related to the