fractal-server 2.11.1__py3-none-any.whl → 2.12.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 (61) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/models/__init__.py +0 -2
  3. fractal_server/app/models/linkuserproject.py +0 -9
  4. fractal_server/app/routes/aux/_job.py +1 -3
  5. fractal_server/app/runner/filenames.py +0 -2
  6. fractal_server/app/runner/shutdown.py +3 -27
  7. fractal_server/config.py +1 -15
  8. fractal_server/main.py +1 -12
  9. fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +67 -0
  10. fractal_server/string_tools.py +0 -21
  11. fractal_server/tasks/utils.py +0 -24
  12. {fractal_server-2.11.1.dist-info → fractal_server-2.12.0a0.dist-info}/METADATA +1 -1
  13. {fractal_server-2.11.1.dist-info → fractal_server-2.12.0a0.dist-info}/RECORD +16 -60
  14. fractal_server/app/models/v1/__init__.py +0 -13
  15. fractal_server/app/models/v1/dataset.py +0 -71
  16. fractal_server/app/models/v1/job.py +0 -101
  17. fractal_server/app/models/v1/project.py +0 -29
  18. fractal_server/app/models/v1/state.py +0 -34
  19. fractal_server/app/models/v1/task.py +0 -85
  20. fractal_server/app/models/v1/workflow.py +0 -133
  21. fractal_server/app/routes/admin/v1.py +0 -377
  22. fractal_server/app/routes/api/v1/__init__.py +0 -26
  23. fractal_server/app/routes/api/v1/_aux_functions.py +0 -478
  24. fractal_server/app/routes/api/v1/dataset.py +0 -554
  25. fractal_server/app/routes/api/v1/job.py +0 -195
  26. fractal_server/app/routes/api/v1/project.py +0 -475
  27. fractal_server/app/routes/api/v1/task.py +0 -203
  28. fractal_server/app/routes/api/v1/task_collection.py +0 -239
  29. fractal_server/app/routes/api/v1/workflow.py +0 -355
  30. fractal_server/app/routes/api/v1/workflowtask.py +0 -187
  31. fractal_server/app/runner/async_wrap_v1.py +0 -27
  32. fractal_server/app/runner/v1/__init__.py +0 -415
  33. fractal_server/app/runner/v1/_common.py +0 -620
  34. fractal_server/app/runner/v1/_local/__init__.py +0 -186
  35. fractal_server/app/runner/v1/_local/_local_config.py +0 -105
  36. fractal_server/app/runner/v1/_local/_submit_setup.py +0 -48
  37. fractal_server/app/runner/v1/_local/executor.py +0 -100
  38. fractal_server/app/runner/v1/_slurm/__init__.py +0 -312
  39. fractal_server/app/runner/v1/_slurm/_submit_setup.py +0 -81
  40. fractal_server/app/runner/v1/_slurm/get_slurm_config.py +0 -163
  41. fractal_server/app/runner/v1/common.py +0 -117
  42. fractal_server/app/runner/v1/handle_failed_job.py +0 -141
  43. fractal_server/app/schemas/v1/__init__.py +0 -37
  44. fractal_server/app/schemas/v1/applyworkflow.py +0 -161
  45. fractal_server/app/schemas/v1/dataset.py +0 -165
  46. fractal_server/app/schemas/v1/dumps.py +0 -64
  47. fractal_server/app/schemas/v1/manifest.py +0 -126
  48. fractal_server/app/schemas/v1/project.py +0 -66
  49. fractal_server/app/schemas/v1/state.py +0 -18
  50. fractal_server/app/schemas/v1/task.py +0 -167
  51. fractal_server/app/schemas/v1/task_collection.py +0 -110
  52. fractal_server/app/schemas/v1/workflow.py +0 -212
  53. fractal_server/tasks/v1/_TaskCollectPip.py +0 -103
  54. fractal_server/tasks/v1/__init__.py +0 -0
  55. fractal_server/tasks/v1/background_operations.py +0 -352
  56. fractal_server/tasks/v1/endpoint_operations.py +0 -156
  57. fractal_server/tasks/v1/get_collection_data.py +0 -14
  58. fractal_server/tasks/v1/utils.py +0 -67
  59. {fractal_server-2.11.1.dist-info → fractal_server-2.12.0a0.dist-info}/LICENSE +0 -0
  60. {fractal_server-2.11.1.dist-info → fractal_server-2.12.0a0.dist-info}/WHEEL +0 -0
  61. {fractal_server-2.11.1.dist-info → fractal_server-2.12.0a0.dist-info}/entry_points.txt +0 -0
@@ -1,620 +0,0 @@
1
- """
2
- Common utilities and routines for runner backends (private API)
3
-
4
- This module includes utilities and routines that are of use to implement
5
- runner backends and that should not be exposed outside of the runner
6
- subsystem.
7
- """
8
- import json
9
- import shutil
10
- import subprocess # nosec
11
- import traceback
12
- from concurrent.futures import Executor
13
- from copy import deepcopy
14
- from functools import partial
15
- from pathlib import Path
16
- from shlex import split as shlex_split
17
- from typing import Any
18
- from typing import Callable
19
- from typing import Optional
20
-
21
- from ....config import get_settings
22
- from ....logger import get_logger
23
- from ....syringe import Inject
24
- from ...models.v1 import Task
25
- from ...models.v1 import WorkflowTask
26
- from ...schemas.v1 import WorkflowTaskStatusTypeV1
27
- from ..exceptions import JobExecutionError
28
- from ..exceptions import TaskExecutionError
29
- from .common import TaskParameters
30
- from .common import write_args_file
31
- from fractal_server.app.runner.filenames import HISTORY_FILENAME_V1
32
- from fractal_server.app.runner.filenames import METADATA_FILENAME_V1
33
- from fractal_server.app.runner.task_files import get_task_file_paths
34
- from fractal_server.string_tools import validate_cmd
35
-
36
-
37
- def no_op_submit_setup_call(
38
- *,
39
- wftask: WorkflowTask,
40
- workflow_dir_local: Path,
41
- workflow_dir_remote: Path,
42
- ) -> dict:
43
- """
44
- Default (no-operation) interface of submit_setup_call.
45
- """
46
- return {}
47
-
48
-
49
- def _task_needs_image_list(_task: Task) -> bool:
50
- """
51
- Whether a task requires `metadata["image"]` in its `args.json` file.
52
-
53
- For details see
54
- https://github.com/fractal-analytics-platform/fractal-server/issues/1237
55
-
56
- Args:
57
- _task: The task to be checked.
58
- """
59
- settings = Inject(get_settings)
60
- exception_task_names = settings.FRACTAL_RUNNER_TASKS_INCLUDE_IMAGE.split(
61
- ";"
62
- )
63
- if _task.name in exception_task_names:
64
- return True
65
- else:
66
- return False
67
-
68
-
69
- def _call_command_wrapper(cmd: str, stdout: Path, stderr: Path) -> None:
70
- """
71
- Call a command and write its stdout and stderr to files
72
-
73
- Raises:
74
- TaskExecutionError: If the `subprocess.run` call returns a positive
75
- exit code
76
- JobExecutionError: If the `subprocess.run` call returns a negative
77
- exit code (e.g. due to the subprocess receiving a
78
- TERM or KILL signal)
79
- """
80
-
81
- validate_cmd(cmd)
82
- # Verify that task command is executable
83
- if shutil.which(shlex_split(cmd)[0]) is None:
84
- msg = (
85
- f'Command "{shlex_split(cmd)[0]}" is not valid. '
86
- "Hint: make sure that it is executable."
87
- )
88
- raise TaskExecutionError(msg)
89
-
90
- fp_stdout = open(stdout, "w")
91
- fp_stderr = open(stderr, "w")
92
- try:
93
- result = subprocess.run( # nosec
94
- shlex_split(cmd),
95
- stderr=fp_stderr,
96
- stdout=fp_stdout,
97
- )
98
- except Exception as e:
99
- raise e
100
- finally:
101
- fp_stdout.close()
102
- fp_stderr.close()
103
-
104
- if result.returncode > 0:
105
- with stderr.open("r") as fp_stderr:
106
- err = fp_stderr.read()
107
- raise TaskExecutionError(err)
108
- elif result.returncode < 0:
109
- raise JobExecutionError(
110
- info=f"Task failed with returncode={result.returncode}"
111
- )
112
-
113
-
114
- def call_single_task(
115
- *,
116
- wftask: WorkflowTask,
117
- task_pars: TaskParameters,
118
- workflow_dir_local: Path,
119
- workflow_dir_remote: Optional[Path] = None,
120
- logger_name: Optional[str] = None,
121
- ) -> TaskParameters:
122
- """
123
- Call a single task
124
-
125
- This assembles the runner arguments (input_paths, output_path, ...) and
126
- wftask arguments (i.e., arguments that are specific to the WorkflowTask,
127
- such as message or index in the dummy task), writes them to file, call the
128
- task executable command passing the arguments file as an input and
129
- assembles the output.
130
-
131
- **Note**: This function is directly submitted to a
132
- `concurrent.futures`-compatible executor, as in
133
-
134
- some_future = executor.submit(call_single_task, ...)
135
-
136
- If the executor then impersonates another user (as in the
137
- `FractalSlurmExecutor`), this function is run by that user. For this
138
- reason, it should not write any file to `workflow_dir_local`, or it may
139
- yield permission errors.
140
-
141
- Args:
142
- wftask:
143
- The workflow task to be called. This includes task specific
144
- arguments via the wftask.args attribute.
145
- task_pars:
146
- The parameters required to run the task which are not specific to
147
- the task, e.g., I/O paths.
148
- workflow_dir_local:
149
- The server-side working directory for workflow execution.
150
- workflow_dir_remote:
151
- The user-side working directory for workflow execution (only
152
- relevant for multi-user executors). If `None`, it is set to be
153
- equal to `workflow_dir_remote`.
154
- logger_name:
155
- Name of the logger
156
-
157
- Returns:
158
- out_task_parameters:
159
- A TaskParameters in which the previous output becomes the input
160
- and where metadata is the metadata dictionary returned by the task
161
- being called.
162
-
163
- Raises:
164
- TaskExecutionError: If the wrapped task raises a task-related error.
165
- This function is responsible of adding debugging
166
- information to the TaskExecutionError, such as task
167
- order and name.
168
- JobExecutionError: If the wrapped task raises a job-related error.
169
- """
170
-
171
- logger = get_logger(logger_name)
172
-
173
- if not workflow_dir_remote:
174
- workflow_dir_remote = workflow_dir_local
175
-
176
- task_files = get_task_file_paths(
177
- workflow_dir_local=workflow_dir_local,
178
- workflow_dir_remote=workflow_dir_remote,
179
- task_order=wftask.order,
180
- task_name=wftask.task.name,
181
- )
182
-
183
- # write args file (by assembling task_pars and wftask.args)
184
- write_args_file(
185
- task_pars.dict(exclude={"history"}),
186
- wftask.args or {},
187
- path=task_files.args,
188
- )
189
-
190
- # assemble full command
191
- cmd = (
192
- f"{wftask.task.command} -j {task_files.args} "
193
- f"--metadata-out {task_files.metadiff}"
194
- )
195
-
196
- try:
197
- _call_command_wrapper(
198
- cmd, stdout=task_files.out, stderr=task_files.err
199
- )
200
- except TaskExecutionError as e:
201
- e.workflow_task_order = wftask.order
202
- e.workflow_task_id = wftask.id
203
- e.task_name = wftask.task.name
204
- raise e
205
-
206
- # This try/except block covers the case of a task that ran successfully but
207
- # did not write the expected metadiff file (ref fractal-server issue #854).
208
- try:
209
- with task_files.metadiff.open("r") as f_metadiff:
210
- diff_metadata = json.load(f_metadiff)
211
- except FileNotFoundError as e:
212
- logger.warning(
213
- f"Skip collection of updated metadata. Original error: {str(e)}"
214
- )
215
- diff_metadata = {}
216
-
217
- # Cover the case where the task wrote `null`, rather than a valid
218
- # dictionary (ref fractal-server issue #878).
219
- if diff_metadata is None:
220
- diff_metadata = {}
221
-
222
- # Prepare updated_metadata
223
- updated_metadata = task_pars.metadata.copy()
224
- updated_metadata.update(diff_metadata)
225
- # Prepare updated_history (note: the expected type for history items is
226
- # defined in `_DatasetHistoryItem`)
227
- wftask_dump = wftask.model_dump(exclude={"task"})
228
- wftask_dump["task"] = wftask.task.model_dump()
229
- new_history_item = dict(
230
- workflowtask=wftask_dump,
231
- status=WorkflowTaskStatusTypeV1.DONE,
232
- parallelization=None,
233
- )
234
- updated_history = task_pars.history.copy()
235
- updated_history.append(new_history_item)
236
-
237
- # Assemble a TaskParameter object
238
- out_task_parameters = TaskParameters(
239
- input_paths=[task_pars.output_path],
240
- output_path=task_pars.output_path,
241
- metadata=updated_metadata,
242
- history=updated_history,
243
- )
244
-
245
- return out_task_parameters
246
-
247
-
248
- def call_single_parallel_task(
249
- component: str,
250
- *,
251
- wftask: WorkflowTask,
252
- task_pars: TaskParameters,
253
- workflow_dir_local: Path,
254
- workflow_dir_remote: Optional[Path] = None,
255
- ) -> Any:
256
- """
257
- Call a single instance of a parallel task
258
-
259
- Parallel tasks need to run in several instances across the parallelization
260
- parameters. This function is responsible of running each single one of
261
- those instances.
262
-
263
- Note:
264
- This function is directly submitted to a
265
- `concurrent.futures`-compatible executor, roughly as in
266
-
267
- some_future = executor.map(call_single_parallel_task, ...)
268
-
269
- If the executor then impersonates another user (as in the
270
- `FractalSlurmExecutor`), this function is run by that user.
271
-
272
- Args:
273
- component:
274
- The parallelization parameter.
275
- wftask:
276
- The task to execute.
277
- task_pars:
278
- The parameters to pass on to the task.
279
- workflow_dir_local:
280
- The server-side working directory for workflow execution.
281
- workflow_dir_remote:
282
- The user-side working directory for workflow execution (only
283
- relevant for multi-user executors).
284
-
285
- Returns:
286
- The `json.load`-ed contents of the metadiff output file, or `None` if
287
- the file is missing.
288
-
289
- Raises:
290
- TaskExecutionError: If the wrapped task raises a task-related error.
291
- This function is responsible of adding debugging
292
- information to the TaskExecutionError, such as task
293
- order and name.
294
- JobExecutionError: If the wrapped task raises a job-related error.
295
- RuntimeError: If the `workflow_dir_local` is falsy.
296
- """
297
- if not workflow_dir_local:
298
- raise RuntimeError
299
- if not workflow_dir_remote:
300
- workflow_dir_remote = workflow_dir_local
301
-
302
- task_files = get_task_file_paths(
303
- workflow_dir_local=workflow_dir_local,
304
- workflow_dir_remote=workflow_dir_remote,
305
- task_order=wftask.order,
306
- task_name=wftask.task.name,
307
- component=component,
308
- )
309
-
310
- # write args file (by assembling task_pars, wftask.args and component)
311
- write_args_file(
312
- task_pars.dict(exclude={"history"}),
313
- wftask.args or {},
314
- dict(component=component),
315
- path=task_files.args,
316
- )
317
-
318
- # assemble full command
319
- cmd = (
320
- f"{wftask.task.command} -j {task_files.args} "
321
- f"--metadata-out {task_files.metadiff}"
322
- )
323
-
324
- try:
325
- _call_command_wrapper(
326
- cmd, stdout=task_files.out, stderr=task_files.err
327
- )
328
- except TaskExecutionError as e:
329
- e.workflow_task_order = wftask.order
330
- e.workflow_task_id = wftask.id
331
- e.task_name = wftask.task.name
332
- raise e
333
-
334
- # JSON-load metadiff file and return its contents (or None)
335
- try:
336
- with task_files.metadiff.open("r") as f:
337
- this_meta_update = json.load(f)
338
- except FileNotFoundError:
339
- this_meta_update = None
340
-
341
- return this_meta_update
342
-
343
-
344
- def trim_TaskParameters(
345
- task_params: TaskParameters,
346
- _task: Task,
347
- ) -> TaskParameters:
348
- """
349
- Return a smaller copy of a TaskParameter object.
350
-
351
- Remove metadata["image"] key/value pair - see issues 1237 and 1242.
352
- (https://github.com/fractal-analytics-platform/fractal-server/issues/1237)
353
- This applies only to parallel tasks with names different from the ones
354
- defined in `_task_needs_image_list`.
355
- """
356
- task_params_slim = deepcopy(task_params)
357
- if not _task_needs_image_list(_task) and _task.is_parallel:
358
- if "image" in task_params_slim.metadata.keys():
359
- task_params_slim.metadata.pop("image")
360
- task_params_slim.history = []
361
- return task_params_slim
362
-
363
-
364
- def call_parallel_task(
365
- *,
366
- executor: Executor,
367
- wftask: WorkflowTask,
368
- task_pars_depend: TaskParameters,
369
- workflow_dir_local: Path,
370
- workflow_dir_remote: Optional[Path] = None,
371
- submit_setup_call: Callable = no_op_submit_setup_call,
372
- logger_name: Optional[str] = None,
373
- ) -> TaskParameters:
374
- """
375
- Collect results from the parallel instances of a parallel task
376
-
377
- Prepare and submit for execution all the single calls of a parallel task,
378
- and return a single TaskParameters instance to be passed on to the
379
- next task.
380
-
381
- **NOTE**: this function is executed by the same user that runs
382
- `fractal-server`, and therefore may not have access to some of user's
383
- files.
384
-
385
- Args:
386
- executor:
387
- The `concurrent.futures.Executor`-compatible executor that will
388
- run the task.
389
- wftask:
390
- The parallel task to run.
391
- task_pars_depend:
392
- The task parameters to be passed on to the parallel task.
393
- workflow_dir_local:
394
- The server-side working directory for workflow execution.
395
- workflow_dir_remote:
396
- The user-side working directory for workflow execution (only
397
- relevant for multi-user executors).
398
- submit_setup_call:
399
- An optional function that computes configuration parameters for
400
- the executor.
401
- logger_name:
402
- Name of the logger
403
-
404
- Returns:
405
- out_task_parameters:
406
- The output task parameters of the parallel task execution, ready to
407
- be passed on to the next task.
408
- """
409
- logger = get_logger(logger_name)
410
-
411
- if not workflow_dir_remote:
412
- workflow_dir_remote = workflow_dir_local
413
-
414
- try:
415
- component_list = task_pars_depend.metadata[
416
- wftask.parallelization_level
417
- ]
418
- except KeyError:
419
- keys = list(task_pars_depend.metadata.keys())
420
- raise RuntimeError(
421
- "WorkflowTask parallelization_level "
422
- f"('{wftask.parallelization_level}') is missing "
423
- f"in metadata keys ({keys})."
424
- )
425
-
426
- # Backend-specific configuration
427
- try:
428
- extra_setup = submit_setup_call(
429
- wftask=wftask,
430
- workflow_dir_local=workflow_dir_local,
431
- workflow_dir_remote=workflow_dir_remote,
432
- )
433
- except Exception as e:
434
- tb = "".join(traceback.format_tb(e.__traceback__))
435
- raise RuntimeError(
436
- f"{type(e)} error in {submit_setup_call=}\n"
437
- f"Original traceback:\n{tb}"
438
- )
439
-
440
- # Preliminary steps
441
- actual_task_pars_depend = trim_TaskParameters(
442
- task_pars_depend, wftask.task
443
- )
444
-
445
- partial_call_task = partial(
446
- call_single_parallel_task,
447
- wftask=wftask,
448
- task_pars=actual_task_pars_depend,
449
- workflow_dir_local=workflow_dir_local,
450
- workflow_dir_remote=workflow_dir_remote,
451
- )
452
-
453
- # Submit tasks for execution. Note that `for _ in map_iter:
454
- # pass` explicitly calls the .result() method for each future, and
455
- # therefore is blocking until the task are complete.
456
- map_iter = executor.map(partial_call_task, component_list, **extra_setup)
457
-
458
- # Wait for execution of parallel tasks, and aggregate updated metadata (ref
459
- # https://github.com/fractal-analytics-platform/fractal-server/issues/802).
460
- # NOTE: Even if we remove the need of aggregating metadata, we must keep
461
- # the iteration over `map_iter` (e.g. as in `for _ in map_iter: pass`), to
462
- # make this call blocking. This is required *also* because otherwise the
463
- # shutdown of a FractalSlurmExecutor while running map() may not work
464
- aggregated_metadata_update: dict[str, Any] = {}
465
- for this_meta_update in map_iter:
466
- # Cover the case where the task wrote `null`, rather than a
467
- # valid dictionary (ref fractal-server issue #878), or where the
468
- # metadiff file was missing.
469
- if this_meta_update is None:
470
- this_meta_update = {}
471
- # Include this_meta_update into aggregated_metadata_update
472
- for key, val in this_meta_update.items():
473
- aggregated_metadata_update.setdefault(key, []).append(val)
474
- if aggregated_metadata_update:
475
- logger.warning(
476
- "Aggregating parallel-taks updated metadata (with keys "
477
- f"{list(aggregated_metadata_update.keys())}).\n"
478
- "This feature is experimental and it may change in "
479
- "future releases."
480
- )
481
-
482
- # Prepare updated_metadata
483
- updated_metadata = task_pars_depend.metadata.copy()
484
- updated_metadata.update(aggregated_metadata_update)
485
-
486
- # Prepare updated_history (note: the expected type for history items is
487
- # defined in `_DatasetHistoryItem`)
488
- wftask_dump = wftask.model_dump(exclude={"task"})
489
- wftask_dump["task"] = wftask.task.model_dump()
490
- new_history_item = dict(
491
- workflowtask=wftask_dump,
492
- status=WorkflowTaskStatusTypeV1.DONE,
493
- parallelization=dict(
494
- parallelization_level=wftask.parallelization_level,
495
- component_list=component_list,
496
- ),
497
- )
498
- updated_history = task_pars_depend.history.copy()
499
- updated_history.append(new_history_item)
500
-
501
- # Assemble a TaskParameter object
502
- out_task_parameters = TaskParameters(
503
- input_paths=[task_pars_depend.output_path],
504
- output_path=task_pars_depend.output_path,
505
- metadata=updated_metadata,
506
- history=updated_history,
507
- )
508
-
509
- return out_task_parameters
510
-
511
-
512
- def execute_tasks(
513
- *,
514
- executor: Executor,
515
- task_list: list[WorkflowTask],
516
- task_pars: TaskParameters,
517
- workflow_dir_local: Path,
518
- workflow_dir_remote: Optional[Path] = None,
519
- submit_setup_call: Callable = no_op_submit_setup_call,
520
- logger_name: str,
521
- ) -> TaskParameters:
522
- """
523
- Submit a list of WorkflowTasks for execution
524
-
525
- **Note:** At the end of each task, write current metadata to
526
- `workflow_dir_local / METADATA_FILENAME`, so that they can be read as part
527
- of the `get_job` endpoint.
528
-
529
- Arguments:
530
- executor:
531
- The `concurrent.futures.Executor`-compatible executor that will
532
- run the task.
533
- task_list:
534
- The list of wftasks to be run
535
- task_pars:
536
- The task parameters to be passed on to the first task of the list.
537
- workflow_dir_local:
538
- The server-side working directory for workflow execution.
539
- workflow_dir_remote:
540
- The user-side working directory for workflow execution (only
541
- relevant for multi-user executors). If `None`, it is set to be
542
- equal to `workflow_dir_local`.
543
- submit_setup_call:
544
- An optional function that computes configuration parameters for
545
- the executor.
546
- logger_name:
547
- Name of the logger
548
-
549
- Returns:
550
- current_task_pars:
551
- A TaskParameters object which constitutes the output of the last
552
- task in the list.
553
- """
554
- if not workflow_dir_remote:
555
- workflow_dir_remote = workflow_dir_local
556
-
557
- logger = get_logger(logger_name)
558
-
559
- current_task_pars = task_pars.copy()
560
-
561
- for this_wftask in task_list:
562
- logger.debug(
563
- f"SUBMIT {this_wftask.order}-th task "
564
- f'(name="{this_wftask.task.name}")'
565
- )
566
- if this_wftask.is_parallel:
567
- current_task_pars = call_parallel_task(
568
- executor=executor,
569
- wftask=this_wftask,
570
- task_pars_depend=current_task_pars,
571
- workflow_dir_local=workflow_dir_local,
572
- workflow_dir_remote=workflow_dir_remote,
573
- submit_setup_call=submit_setup_call,
574
- logger_name=logger_name,
575
- )
576
- else:
577
- # Call backend-specific submit_setup_call
578
- try:
579
- extra_setup = submit_setup_call(
580
- wftask=this_wftask,
581
- workflow_dir_local=workflow_dir_local,
582
- workflow_dir_remote=workflow_dir_remote,
583
- )
584
- except Exception as e:
585
- tb = "".join(traceback.format_tb(e.__traceback__))
586
- raise RuntimeError(
587
- f"{type(e)} error in {submit_setup_call=}\n"
588
- f"Original traceback:\n{tb}"
589
- )
590
- # NOTE: executor.submit(call_single_task, ...) is non-blocking,
591
- # i.e. the returned future may have `this_wftask_future.done() =
592
- # False`. We make it blocking right away, by calling `.result()`
593
- # NOTE: do not use trim_TaskParameters for non-parallel tasks,
594
- # since the `task_pars` argument in `call_single_task` is also used
595
- # as a basis for new `metadata`.
596
- this_wftask_future = executor.submit(
597
- call_single_task,
598
- wftask=this_wftask,
599
- task_pars=current_task_pars,
600
- workflow_dir_local=workflow_dir_local,
601
- workflow_dir_remote=workflow_dir_remote,
602
- logger_name=logger_name,
603
- **extra_setup,
604
- )
605
- # Wait for the future result (blocking)
606
- current_task_pars = this_wftask_future.result()
607
- logger.debug(
608
- f"END {this_wftask.order}-th task "
609
- f'(name="{this_wftask.task.name}")'
610
- )
611
-
612
- # Write most recent metadata to METADATA_FILENAME
613
- with open(workflow_dir_local / METADATA_FILENAME_V1, "w") as f:
614
- json.dump(current_task_pars.metadata, f, indent=2)
615
-
616
- # Write most recent metadata to HISTORY_FILENAME
617
- with open(workflow_dir_local / HISTORY_FILENAME_V1, "w") as f:
618
- json.dump(current_task_pars.history, f, indent=2)
619
-
620
- return current_task_pars