fractal-server 2.0.6__py3-none-any.whl → 2.2.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.
- fractal_server/__init__.py +1 -1
- fractal_server/app/routes/admin/v1.py +2 -4
- fractal_server/app/routes/admin/v2.py +2 -4
- fractal_server/app/routes/api/v1/_aux_functions.py +24 -0
- fractal_server/app/routes/api/v1/job.py +3 -4
- fractal_server/app/routes/api/v1/project.py +28 -18
- fractal_server/app/routes/api/v2/_aux_functions.py +35 -12
- fractal_server/app/routes/api/v2/job.py +3 -4
- fractal_server/app/routes/api/v2/project.py +21 -0
- fractal_server/app/routes/api/v2/submit.py +36 -15
- fractal_server/app/routes/aux/_job.py +3 -1
- fractal_server/app/routes/aux/_runner.py +3 -3
- fractal_server/app/runner/executors/slurm/executor.py +169 -68
- fractal_server/app/runner/shutdown.py +88 -0
- fractal_server/app/runner/task_files.py +59 -27
- fractal_server/app/runner/v1/__init__.py +38 -27
- fractal_server/app/runner/v1/_common.py +53 -51
- fractal_server/app/runner/v1/_local/__init__.py +12 -11
- fractal_server/app/runner/v1/_local/_submit_setup.py +4 -4
- fractal_server/app/runner/v1/_slurm/__init__.py +16 -16
- fractal_server/app/runner/v1/_slurm/_submit_setup.py +11 -10
- fractal_server/app/runner/v1/_slurm/get_slurm_config.py +6 -6
- fractal_server/app/runner/v2/__init__.py +47 -15
- fractal_server/app/runner/v2/_local/__init__.py +12 -11
- fractal_server/app/runner/v2/_local/_local_config.py +1 -1
- fractal_server/app/runner/v2/_local/_submit_setup.py +4 -4
- fractal_server/app/runner/v2/_local_experimental/__init__.py +145 -0
- fractal_server/app/runner/v2/_local_experimental/_local_config.py +108 -0
- fractal_server/app/runner/v2/_local_experimental/_submit_setup.py +42 -0
- fractal_server/app/runner/v2/_local_experimental/executor.py +152 -0
- fractal_server/app/runner/v2/_slurm/__init__.py +10 -10
- fractal_server/app/runner/v2/_slurm/_submit_setup.py +11 -10
- fractal_server/app/runner/v2/_slurm/get_slurm_config.py +6 -6
- fractal_server/app/runner/v2/runner.py +17 -15
- fractal_server/app/runner/v2/runner_functions.py +38 -38
- fractal_server/app/runner/v2/runner_functions_low_level.py +12 -6
- fractal_server/app/security/__init__.py +4 -5
- fractal_server/config.py +35 -1
- fractal_server/gunicorn_fractal.py +40 -0
- fractal_server/{logger/__init__.py → logger.py} +2 -2
- fractal_server/main.py +45 -26
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0a0.dist-info}/METADATA +1 -1
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0a0.dist-info}/RECORD +46 -41
- fractal_server/logger/gunicorn_logger.py +0 -19
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0a0.dist-info}/LICENSE +0 -0
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0a0.dist-info}/WHEEL +0 -0
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0a0.dist-info}/entry_points.txt +0 -0
@@ -1,32 +1,55 @@
|
|
1
1
|
from pathlib import Path
|
2
2
|
from typing import Optional
|
3
|
+
from typing import Union
|
4
|
+
|
5
|
+
from fractal_server.tasks.utils import slugify_task_name
|
3
6
|
|
4
7
|
|
5
8
|
def sanitize_component(value: str) -> str:
|
6
9
|
"""
|
7
10
|
Remove {" ", "/", "."} form a string, e.g. going from
|
8
11
|
'plate.zarr/B/03/0' to 'plate_zarr_B_03_0'.
|
12
|
+
|
13
|
+
Args:
|
14
|
+
value: Input strig
|
9
15
|
"""
|
10
16
|
return value.replace(" ", "_").replace("/", "_").replace(".", "_")
|
11
17
|
|
12
18
|
|
19
|
+
def task_subfolder_name(order: Union[int, str], task_name: str) -> str:
|
20
|
+
"""
|
21
|
+
Get name of task-specific subfolder.
|
22
|
+
|
23
|
+
Args:
|
24
|
+
order:
|
25
|
+
task_name:
|
26
|
+
"""
|
27
|
+
task_name_slug = slugify_task_name(task_name)
|
28
|
+
return f"{order}_{task_name_slug}"
|
29
|
+
|
30
|
+
|
13
31
|
class TaskFiles:
|
14
32
|
"""
|
15
33
|
Group all file paths pertaining to a task
|
16
34
|
|
17
35
|
Attributes:
|
18
|
-
|
36
|
+
workflow_dir_local:
|
19
37
|
Server-owned directory to store all task-execution-related relevant
|
20
|
-
files
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
38
|
+
files. Note: users cannot write directly to this folder.
|
39
|
+
workflow_dir_remote:
|
40
|
+
User-side directory with the same scope as `workflow_dir_local`,
|
41
|
+
and where a user can write.
|
42
|
+
subfolder_name:
|
43
|
+
Name of task-specific subfolder
|
44
|
+
remote_subfolder:
|
45
|
+
Path to user-side task-specific subfolder
|
46
|
+
task_name:
|
47
|
+
Name of the task
|
25
48
|
task_order:
|
26
49
|
Positional order of the task within a workflow.
|
27
50
|
component:
|
28
|
-
Specific component to run the task for (relevant for tasks
|
29
|
-
|
51
|
+
Specific component to run the task for (relevant for tasks to be
|
52
|
+
executed in parallel over many components).
|
30
53
|
file_prefix:
|
31
54
|
Prefix for all task-related files.
|
32
55
|
args:
|
@@ -39,12 +62,16 @@ class TaskFiles:
|
|
39
62
|
Path for task-execution stderr.
|
40
63
|
"""
|
41
64
|
|
42
|
-
|
43
|
-
|
65
|
+
workflow_dir_local: Path
|
66
|
+
workflow_dir_remote: Path
|
67
|
+
remote_subfolder: Path
|
68
|
+
subfolder_name: str
|
69
|
+
task_name: str
|
44
70
|
task_order: Optional[int] = None
|
45
71
|
component: Optional[str] = None
|
46
72
|
|
47
73
|
file_prefix: str
|
74
|
+
file_prefix_with_subfolder: str
|
48
75
|
args: Path
|
49
76
|
out: Path
|
50
77
|
err: Path
|
@@ -53,14 +80,16 @@ class TaskFiles:
|
|
53
80
|
|
54
81
|
def __init__(
|
55
82
|
self,
|
56
|
-
|
57
|
-
|
83
|
+
workflow_dir_local: Path,
|
84
|
+
workflow_dir_remote: Path,
|
85
|
+
task_name: str,
|
58
86
|
task_order: Optional[int] = None,
|
59
87
|
component: Optional[str] = None,
|
60
88
|
):
|
61
|
-
self.
|
62
|
-
self.
|
89
|
+
self.workflow_dir_local = workflow_dir_local
|
90
|
+
self.workflow_dir_remote = workflow_dir_remote
|
63
91
|
self.task_order = task_order
|
92
|
+
self.task_name = task_name
|
64
93
|
self.component = component
|
65
94
|
|
66
95
|
if self.component is not None:
|
@@ -72,32 +101,35 @@ class TaskFiles:
|
|
72
101
|
if self.task_order is not None:
|
73
102
|
order = str(self.task_order)
|
74
103
|
else:
|
75
|
-
order = "
|
104
|
+
order = "0"
|
76
105
|
self.file_prefix = f"{order}{component_safe}"
|
77
|
-
self.
|
78
|
-
|
79
|
-
|
80
|
-
self.
|
106
|
+
self.subfolder_name = task_subfolder_name(
|
107
|
+
order=order, task_name=self.task_name
|
108
|
+
)
|
109
|
+
self.remote_subfolder = self.workflow_dir_remote / self.subfolder_name
|
110
|
+
self.args = self.remote_subfolder / f"{self.file_prefix}.args.json"
|
111
|
+
self.out = self.remote_subfolder / f"{self.file_prefix}.out"
|
112
|
+
self.err = self.remote_subfolder / f"{self.file_prefix}.err"
|
113
|
+
self.log = self.remote_subfolder / f"{self.file_prefix}.log"
|
81
114
|
self.metadiff = (
|
82
|
-
self.
|
115
|
+
self.remote_subfolder / f"{self.file_prefix}.metadiff.json"
|
83
116
|
)
|
84
117
|
|
85
118
|
|
86
119
|
def get_task_file_paths(
|
87
|
-
|
88
|
-
|
120
|
+
workflow_dir_local: Path,
|
121
|
+
workflow_dir_remote: Path,
|
122
|
+
task_name: str,
|
89
123
|
task_order: Optional[int] = None,
|
90
124
|
component: Optional[str] = None,
|
91
125
|
) -> TaskFiles:
|
92
126
|
"""
|
93
127
|
Return the corrisponding TaskFiles object
|
94
|
-
|
95
|
-
This function is mainly used as a cache to avoid instantiating needless
|
96
|
-
objects.
|
97
128
|
"""
|
98
129
|
return TaskFiles(
|
99
|
-
|
100
|
-
|
130
|
+
workflow_dir_local=workflow_dir_local,
|
131
|
+
workflow_dir_remote=workflow_dir_remote,
|
132
|
+
task_name=task_name,
|
101
133
|
task_order=task_order,
|
102
134
|
component=component,
|
103
135
|
)
|
@@ -33,7 +33,11 @@ from ...models.v1 import WorkflowTask
|
|
33
33
|
from ...schemas.v1 import JobStatusTypeV1
|
34
34
|
from ..exceptions import JobExecutionError
|
35
35
|
from ..exceptions import TaskExecutionError
|
36
|
+
from ..executors.slurm._subprocess_run_as_user import (
|
37
|
+
_mkdir_as_user,
|
38
|
+
)
|
36
39
|
from ..filenames import WORKFLOW_LOG_FILENAME
|
40
|
+
from ..task_files import task_subfolder_name
|
37
41
|
from ._local import process_workflow as local_process_workflow
|
38
42
|
from ._slurm import process_workflow as slurm_process_workflow
|
39
43
|
from .common import close_job_logger
|
@@ -137,41 +141,48 @@ async def submit_workflow(
|
|
137
141
|
# Define and create server-side working folder
|
138
142
|
project_id = workflow.project_id
|
139
143
|
timestamp_string = get_timestamp().strftime("%Y%m%d_%H%M%S")
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
f"_{timestamp_string}"
|
145
|
-
)
|
146
|
-
).resolve()
|
144
|
+
WORKFLOW_DIR_LOCAL = settings.FRACTAL_RUNNER_WORKING_BASE_DIR / (
|
145
|
+
f"proj_{project_id:07d}_wf_{workflow_id:07d}_job_{job_id:07d}"
|
146
|
+
f"_{timestamp_string}"
|
147
|
+
)
|
147
148
|
|
148
|
-
if
|
149
|
-
raise RuntimeError(
|
149
|
+
if WORKFLOW_DIR_LOCAL.exists():
|
150
|
+
raise RuntimeError(
|
151
|
+
f"Workflow dir {WORKFLOW_DIR_LOCAL} already exists."
|
152
|
+
)
|
150
153
|
|
151
|
-
# Create WORKFLOW_DIR
|
154
|
+
# Create WORKFLOW_DIR
|
152
155
|
original_umask = os.umask(0)
|
153
|
-
|
156
|
+
WORKFLOW_DIR_LOCAL.mkdir(parents=True, mode=0o755)
|
154
157
|
os.umask(original_umask)
|
155
158
|
|
156
|
-
# Define and create
|
159
|
+
# Define and create WORKFLOW_DIR_REMOTE
|
157
160
|
if FRACTAL_RUNNER_BACKEND == "local":
|
158
|
-
|
161
|
+
WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
|
159
162
|
elif FRACTAL_RUNNER_BACKEND == "slurm":
|
160
|
-
|
161
|
-
|
162
|
-
_mkdir_as_user,
|
163
|
+
WORKFLOW_DIR_REMOTE = (
|
164
|
+
Path(user_cache_dir) / WORKFLOW_DIR_LOCAL.name
|
163
165
|
)
|
166
|
+
_mkdir_as_user(folder=str(WORKFLOW_DIR_REMOTE), user=slurm_user)
|
164
167
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
168
|
+
# Create all tasks subfolders
|
169
|
+
for order in range(job.first_task_index, job.last_task_index + 1):
|
170
|
+
subfolder_name = task_subfolder_name(
|
171
|
+
order=order,
|
172
|
+
task_name=workflow.task_list[order].task.name,
|
173
|
+
)
|
174
|
+
original_umask = os.umask(0)
|
175
|
+
(WORKFLOW_DIR_LOCAL / subfolder_name).mkdir(mode=0o755)
|
176
|
+
os.umask(original_umask)
|
177
|
+
if FRACTAL_RUNNER_BACKEND == "slurm":
|
178
|
+
_mkdir_as_user(
|
179
|
+
folder=str(WORKFLOW_DIR_REMOTE / subfolder_name),
|
180
|
+
user=slurm_user,
|
181
|
+
)
|
171
182
|
|
172
183
|
# Update db
|
173
|
-
job.working_dir =
|
174
|
-
job.working_dir_user =
|
184
|
+
job.working_dir = WORKFLOW_DIR_LOCAL.as_posix()
|
185
|
+
job.working_dir_user = WORKFLOW_DIR_REMOTE.as_posix()
|
175
186
|
db_sync.merge(job)
|
176
187
|
db_sync.commit()
|
177
188
|
|
@@ -192,7 +203,7 @@ async def submit_workflow(
|
|
192
203
|
|
193
204
|
# Write logs
|
194
205
|
logger_name = f"WF{workflow_id}_job{job_id}"
|
195
|
-
log_file_path =
|
206
|
+
log_file_path = WORKFLOW_DIR_LOCAL / WORKFLOW_LOG_FILENAME
|
196
207
|
logger = set_logger(
|
197
208
|
logger_name=logger_name,
|
198
209
|
log_file_path=log_file_path,
|
@@ -239,8 +250,8 @@ async def submit_workflow(
|
|
239
250
|
slurm_user=slurm_user,
|
240
251
|
slurm_account=job.slurm_account,
|
241
252
|
user_cache_dir=user_cache_dir,
|
242
|
-
|
243
|
-
|
253
|
+
workflow_dir_local=WORKFLOW_DIR_LOCAL,
|
254
|
+
workflow_dir_remote=WORKFLOW_DIR_REMOTE,
|
244
255
|
logger_name=logger_name,
|
245
256
|
worker_init=worker_init,
|
246
257
|
first_task_index=job.first_task_index,
|
@@ -36,8 +36,8 @@ from fractal_server.app.runner.task_files import get_task_file_paths
|
|
36
36
|
def no_op_submit_setup_call(
|
37
37
|
*,
|
38
38
|
wftask: WorkflowTask,
|
39
|
-
|
40
|
-
|
39
|
+
workflow_dir_local: Path,
|
40
|
+
workflow_dir_remote: Path,
|
41
41
|
) -> dict:
|
42
42
|
"""
|
43
43
|
Default (no-operation) interface of submit_setup_call.
|
@@ -113,8 +113,8 @@ def call_single_task(
|
|
113
113
|
*,
|
114
114
|
wftask: WorkflowTask,
|
115
115
|
task_pars: TaskParameters,
|
116
|
-
|
117
|
-
|
116
|
+
workflow_dir_local: Path,
|
117
|
+
workflow_dir_remote: Optional[Path] = None,
|
118
118
|
logger_name: Optional[str] = None,
|
119
119
|
) -> TaskParameters:
|
120
120
|
"""
|
@@ -133,8 +133,8 @@ def call_single_task(
|
|
133
133
|
|
134
134
|
If the executor then impersonates another user (as in the
|
135
135
|
`FractalSlurmExecutor`), this function is run by that user. For this
|
136
|
-
reason, it should not write any file to
|
137
|
-
permission errors.
|
136
|
+
reason, it should not write any file to `workflow_dir_local`, or it may
|
137
|
+
yield permission errors.
|
138
138
|
|
139
139
|
Args:
|
140
140
|
wftask:
|
@@ -143,12 +143,12 @@ def call_single_task(
|
|
143
143
|
task_pars:
|
144
144
|
The parameters required to run the task which are not specific to
|
145
145
|
the task, e.g., I/O paths.
|
146
|
-
|
146
|
+
workflow_dir_local:
|
147
147
|
The server-side working directory for workflow execution.
|
148
|
-
|
148
|
+
workflow_dir_remote:
|
149
149
|
The user-side working directory for workflow execution (only
|
150
150
|
relevant for multi-user executors). If `None`, it is set to be
|
151
|
-
equal to `
|
151
|
+
equal to `workflow_dir_remote`.
|
152
152
|
logger_name:
|
153
153
|
Name of the logger
|
154
154
|
|
@@ -164,18 +164,18 @@ def call_single_task(
|
|
164
164
|
information to the TaskExecutionError, such as task
|
165
165
|
order and name.
|
166
166
|
JobExecutionError: If the wrapped task raises a job-related error.
|
167
|
-
RuntimeError: If the `workflow_dir` is falsy.
|
168
167
|
"""
|
169
168
|
|
170
169
|
logger = get_logger(logger_name)
|
171
170
|
|
172
|
-
if not
|
173
|
-
|
171
|
+
if not workflow_dir_remote:
|
172
|
+
workflow_dir_remote = workflow_dir_local
|
174
173
|
|
175
174
|
task_files = get_task_file_paths(
|
176
|
-
|
177
|
-
|
175
|
+
workflow_dir_local=workflow_dir_local,
|
176
|
+
workflow_dir_remote=workflow_dir_remote,
|
178
177
|
task_order=wftask.order,
|
178
|
+
task_name=wftask.task.name,
|
179
179
|
)
|
180
180
|
|
181
181
|
# write args file (by assembling task_pars and wftask.args)
|
@@ -248,8 +248,8 @@ def call_single_parallel_task(
|
|
248
248
|
*,
|
249
249
|
wftask: WorkflowTask,
|
250
250
|
task_pars: TaskParameters,
|
251
|
-
|
252
|
-
|
251
|
+
workflow_dir_local: Path,
|
252
|
+
workflow_dir_remote: Optional[Path] = None,
|
253
253
|
) -> Any:
|
254
254
|
"""
|
255
255
|
Call a single instance of a parallel task
|
@@ -274,9 +274,9 @@ def call_single_parallel_task(
|
|
274
274
|
The task to execute.
|
275
275
|
task_pars:
|
276
276
|
The parameters to pass on to the task.
|
277
|
-
|
277
|
+
workflow_dir_local:
|
278
278
|
The server-side working directory for workflow execution.
|
279
|
-
|
279
|
+
workflow_dir_remote:
|
280
280
|
The user-side working directory for workflow execution (only
|
281
281
|
relevant for multi-user executors).
|
282
282
|
|
@@ -290,17 +290,18 @@ def call_single_parallel_task(
|
|
290
290
|
information to the TaskExecutionError, such as task
|
291
291
|
order and name.
|
292
292
|
JobExecutionError: If the wrapped task raises a job-related error.
|
293
|
-
RuntimeError: If the `
|
293
|
+
RuntimeError: If the `workflow_dir_local` is falsy.
|
294
294
|
"""
|
295
|
-
if not
|
295
|
+
if not workflow_dir_local:
|
296
296
|
raise RuntimeError
|
297
|
-
if not
|
298
|
-
|
297
|
+
if not workflow_dir_remote:
|
298
|
+
workflow_dir_remote = workflow_dir_local
|
299
299
|
|
300
300
|
task_files = get_task_file_paths(
|
301
|
-
|
302
|
-
|
301
|
+
workflow_dir_local=workflow_dir_local,
|
302
|
+
workflow_dir_remote=workflow_dir_remote,
|
303
303
|
task_order=wftask.order,
|
304
|
+
task_name=wftask.task.name,
|
304
305
|
component=component,
|
305
306
|
)
|
306
307
|
|
@@ -363,8 +364,8 @@ def call_parallel_task(
|
|
363
364
|
executor: Executor,
|
364
365
|
wftask: WorkflowTask,
|
365
366
|
task_pars_depend: TaskParameters,
|
366
|
-
|
367
|
-
|
367
|
+
workflow_dir_local: Path,
|
368
|
+
workflow_dir_remote: Optional[Path] = None,
|
368
369
|
submit_setup_call: Callable = no_op_submit_setup_call,
|
369
370
|
logger_name: Optional[str] = None,
|
370
371
|
) -> TaskParameters:
|
@@ -387,9 +388,9 @@ def call_parallel_task(
|
|
387
388
|
The parallel task to run.
|
388
389
|
task_pars_depend:
|
389
390
|
The task parameters to be passed on to the parallel task.
|
390
|
-
|
391
|
+
workflow_dir_local:
|
391
392
|
The server-side working directory for workflow execution.
|
392
|
-
|
393
|
+
workflow_dir_remote:
|
393
394
|
The user-side working directory for workflow execution (only
|
394
395
|
relevant for multi-user executors).
|
395
396
|
submit_setup_call:
|
@@ -405,8 +406,8 @@ def call_parallel_task(
|
|
405
406
|
"""
|
406
407
|
logger = get_logger(logger_name)
|
407
408
|
|
408
|
-
if not
|
409
|
-
|
409
|
+
if not workflow_dir_remote:
|
410
|
+
workflow_dir_remote = workflow_dir_local
|
410
411
|
|
411
412
|
try:
|
412
413
|
component_list = task_pars_depend.metadata[
|
@@ -424,8 +425,8 @@ def call_parallel_task(
|
|
424
425
|
try:
|
425
426
|
extra_setup = submit_setup_call(
|
426
427
|
wftask=wftask,
|
427
|
-
|
428
|
-
|
428
|
+
workflow_dir_local=workflow_dir_local,
|
429
|
+
workflow_dir_remote=workflow_dir_remote,
|
429
430
|
)
|
430
431
|
except Exception as e:
|
431
432
|
tb = "".join(traceback.format_tb(e.__traceback__))
|
@@ -443,8 +444,8 @@ def call_parallel_task(
|
|
443
444
|
call_single_parallel_task,
|
444
445
|
wftask=wftask,
|
445
446
|
task_pars=actual_task_pars_depend,
|
446
|
-
|
447
|
-
|
447
|
+
workflow_dir_local=workflow_dir_local,
|
448
|
+
workflow_dir_remote=workflow_dir_remote,
|
448
449
|
)
|
449
450
|
|
450
451
|
# Submit tasks for execution. Note that `for _ in map_iter:
|
@@ -511,16 +512,17 @@ def execute_tasks(
|
|
511
512
|
executor: Executor,
|
512
513
|
task_list: list[WorkflowTask],
|
513
514
|
task_pars: TaskParameters,
|
514
|
-
|
515
|
-
|
515
|
+
workflow_dir_local: Path,
|
516
|
+
workflow_dir_remote: Optional[Path] = None,
|
516
517
|
submit_setup_call: Callable = no_op_submit_setup_call,
|
517
518
|
logger_name: str,
|
518
519
|
) -> TaskParameters:
|
519
520
|
"""
|
520
521
|
Submit a list of WorkflowTasks for execution
|
521
522
|
|
522
|
-
**Note:** At the end of each task, write current metadata to
|
523
|
-
METADATA_FILENAME`, so that they can be read as part
|
523
|
+
**Note:** At the end of each task, write current metadata to
|
524
|
+
`workflow_dir_local / METADATA_FILENAME`, so that they can be read as part
|
525
|
+
of the [`get_job`
|
524
526
|
endpoint](../../api/v1/job/#fractal_server.app.routes.api.v1.job.get_job).
|
525
527
|
|
526
528
|
Arguments:
|
@@ -531,12 +533,12 @@ def execute_tasks(
|
|
531
533
|
The list of wftasks to be run
|
532
534
|
task_pars:
|
533
535
|
The task parameters to be passed on to the first task of the list.
|
534
|
-
|
536
|
+
workflow_dir_local:
|
535
537
|
The server-side working directory for workflow execution.
|
536
|
-
|
538
|
+
workflow_dir_remote:
|
537
539
|
The user-side working directory for workflow execution (only
|
538
540
|
relevant for multi-user executors). If `None`, it is set to be
|
539
|
-
equal to `
|
541
|
+
equal to `workflow_dir_local`.
|
540
542
|
submit_setup_call:
|
541
543
|
An optional function that computes configuration parameters for
|
542
544
|
the executor.
|
@@ -548,8 +550,8 @@ def execute_tasks(
|
|
548
550
|
A TaskParameters object which constitutes the output of the last
|
549
551
|
task in the list.
|
550
552
|
"""
|
551
|
-
if not
|
552
|
-
|
553
|
+
if not workflow_dir_remote:
|
554
|
+
workflow_dir_remote = workflow_dir_local
|
553
555
|
|
554
556
|
logger = get_logger(logger_name)
|
555
557
|
|
@@ -565,8 +567,8 @@ def execute_tasks(
|
|
565
567
|
executor=executor,
|
566
568
|
wftask=this_wftask,
|
567
569
|
task_pars_depend=current_task_pars,
|
568
|
-
|
569
|
-
|
570
|
+
workflow_dir_local=workflow_dir_local,
|
571
|
+
workflow_dir_remote=workflow_dir_remote,
|
570
572
|
submit_setup_call=submit_setup_call,
|
571
573
|
logger_name=logger_name,
|
572
574
|
)
|
@@ -575,8 +577,8 @@ def execute_tasks(
|
|
575
577
|
try:
|
576
578
|
extra_setup = submit_setup_call(
|
577
579
|
wftask=this_wftask,
|
578
|
-
|
579
|
-
|
580
|
+
workflow_dir_local=workflow_dir_local,
|
581
|
+
workflow_dir_remote=workflow_dir_remote,
|
580
582
|
)
|
581
583
|
except Exception as e:
|
582
584
|
tb = "".join(traceback.format_tb(e.__traceback__))
|
@@ -594,8 +596,8 @@ def execute_tasks(
|
|
594
596
|
call_single_task,
|
595
597
|
wftask=this_wftask,
|
596
598
|
task_pars=current_task_pars,
|
597
|
-
|
598
|
-
|
599
|
+
workflow_dir_local=workflow_dir_local,
|
600
|
+
workflow_dir_remote=workflow_dir_remote,
|
599
601
|
logger_name=logger_name,
|
600
602
|
**extra_setup,
|
601
603
|
)
|
@@ -607,11 +609,11 @@ def execute_tasks(
|
|
607
609
|
)
|
608
610
|
|
609
611
|
# Write most recent metadata to METADATA_FILENAME
|
610
|
-
with open(
|
612
|
+
with open(workflow_dir_local / METADATA_FILENAME, "w") as f:
|
611
613
|
json.dump(current_task_pars.metadata, f, indent=2)
|
612
614
|
|
613
615
|
# Write most recent metadata to HISTORY_FILENAME
|
614
|
-
with open(
|
616
|
+
with open(workflow_dir_local / HISTORY_FILENAME, "w") as f:
|
615
617
|
json.dump(current_task_pars.history, f, indent=2)
|
616
618
|
|
617
619
|
return current_task_pars
|
@@ -40,7 +40,7 @@ def _process_workflow(
|
|
40
40
|
input_metadata: dict[str, Any],
|
41
41
|
input_history: list[dict[str, Any]],
|
42
42
|
logger_name: str,
|
43
|
-
|
43
|
+
workflow_dir_local: Path,
|
44
44
|
first_task_index: int,
|
45
45
|
last_task_index: int,
|
46
46
|
) -> dict[str, Any]:
|
@@ -66,8 +66,8 @@ def _process_workflow(
|
|
66
66
|
metadata=input_metadata,
|
67
67
|
history=input_history,
|
68
68
|
),
|
69
|
-
|
70
|
-
|
69
|
+
workflow_dir_local=workflow_dir_local,
|
70
|
+
workflow_dir_remote=workflow_dir_local,
|
71
71
|
logger_name=logger_name,
|
72
72
|
submit_setup_call=_local_submit_setup,
|
73
73
|
)
|
@@ -85,8 +85,8 @@ async def process_workflow(
|
|
85
85
|
input_metadata: dict[str, Any],
|
86
86
|
input_history: list[dict[str, Any]],
|
87
87
|
logger_name: str,
|
88
|
-
|
89
|
-
|
88
|
+
workflow_dir_local: Path,
|
89
|
+
workflow_dir_remote: Optional[Path] = None,
|
90
90
|
slurm_user: Optional[str] = None,
|
91
91
|
slurm_account: Optional[str] = None,
|
92
92
|
user_cache_dir: Optional[str] = None,
|
@@ -115,12 +115,13 @@ async def process_workflow(
|
|
115
115
|
Initial metadata, passed to the first task
|
116
116
|
logger_name:
|
117
117
|
Name of the logger to log information on the run to
|
118
|
-
|
118
|
+
workflow_dir_local:
|
119
119
|
Working directory for this run.
|
120
|
-
|
120
|
+
workflow_dir_remote:
|
121
121
|
Working directory for this run, on the user side. This argument is
|
122
122
|
present for compatibility with the standard backend interface, but
|
123
|
-
for the `local` backend it cannot be different from
|
123
|
+
for the `local` backend it cannot be different from
|
124
|
+
`workflow_dir_local`.
|
124
125
|
slurm_user:
|
125
126
|
Username to impersonate to run the workflow. This argument is
|
126
127
|
present for compatibility with the standard backend interface, but
|
@@ -157,10 +158,10 @@ async def process_workflow(
|
|
157
158
|
of the workflow
|
158
159
|
"""
|
159
160
|
|
160
|
-
if
|
161
|
+
if workflow_dir_remote and (workflow_dir_remote != workflow_dir_local):
|
161
162
|
raise NotImplementedError(
|
162
163
|
"Local backend does not support different directories "
|
163
|
-
f"{
|
164
|
+
f"{workflow_dir_local=} and {workflow_dir_remote=}"
|
164
165
|
)
|
165
166
|
|
166
167
|
# Set values of first_task_index and last_task_index
|
@@ -178,7 +179,7 @@ async def process_workflow(
|
|
178
179
|
input_metadata=input_metadata,
|
179
180
|
input_history=input_history,
|
180
181
|
logger_name=logger_name,
|
181
|
-
|
182
|
+
workflow_dir_local=workflow_dir_local,
|
182
183
|
first_task_index=first_task_index,
|
183
184
|
last_task_index=last_task_index,
|
184
185
|
)
|
@@ -21,8 +21,8 @@ from ._local_config import get_local_backend_config
|
|
21
21
|
def _local_submit_setup(
|
22
22
|
*,
|
23
23
|
wftask: WorkflowTask,
|
24
|
-
|
25
|
-
|
24
|
+
workflow_dir_local: Optional[Path] = None,
|
25
|
+
workflow_dir_remote: Optional[Path] = None,
|
26
26
|
) -> dict[str, object]:
|
27
27
|
"""
|
28
28
|
Collect WorfklowTask-specific configuration parameters from different
|
@@ -31,9 +31,9 @@ def _local_submit_setup(
|
|
31
31
|
Arguments:
|
32
32
|
wftask:
|
33
33
|
WorkflowTask for which the configuration is to be assembled
|
34
|
-
|
34
|
+
workflow_dir_local:
|
35
35
|
Not used in this function.
|
36
|
-
|
36
|
+
workflow_dir_remote:
|
37
37
|
Not used in this function.
|
38
38
|
|
39
39
|
Returns:
|