fractal-server 2.11.0a10__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 (65) 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/models/v2/dataset.py +0 -4
  5. fractal_server/app/models/v2/workflowtask.py +0 -4
  6. fractal_server/app/routes/aux/_job.py +1 -3
  7. fractal_server/app/runner/filenames.py +0 -2
  8. fractal_server/app/runner/shutdown.py +3 -27
  9. fractal_server/config.py +1 -15
  10. fractal_server/main.py +1 -12
  11. fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +67 -0
  12. fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +54 -0
  13. fractal_server/string_tools.py +0 -21
  14. fractal_server/tasks/utils.py +0 -24
  15. {fractal_server-2.11.0a10.dist-info → fractal_server-2.12.0a0.dist-info}/METADATA +1 -1
  16. {fractal_server-2.11.0a10.dist-info → fractal_server-2.12.0a0.dist-info}/RECORD +19 -63
  17. fractal_server/app/models/v1/__init__.py +0 -13
  18. fractal_server/app/models/v1/dataset.py +0 -71
  19. fractal_server/app/models/v1/job.py +0 -101
  20. fractal_server/app/models/v1/project.py +0 -29
  21. fractal_server/app/models/v1/state.py +0 -34
  22. fractal_server/app/models/v1/task.py +0 -85
  23. fractal_server/app/models/v1/workflow.py +0 -133
  24. fractal_server/app/routes/admin/v1.py +0 -377
  25. fractal_server/app/routes/api/v1/__init__.py +0 -26
  26. fractal_server/app/routes/api/v1/_aux_functions.py +0 -478
  27. fractal_server/app/routes/api/v1/dataset.py +0 -554
  28. fractal_server/app/routes/api/v1/job.py +0 -195
  29. fractal_server/app/routes/api/v1/project.py +0 -475
  30. fractal_server/app/routes/api/v1/task.py +0 -203
  31. fractal_server/app/routes/api/v1/task_collection.py +0 -239
  32. fractal_server/app/routes/api/v1/workflow.py +0 -355
  33. fractal_server/app/routes/api/v1/workflowtask.py +0 -187
  34. fractal_server/app/runner/async_wrap_v1.py +0 -27
  35. fractal_server/app/runner/v1/__init__.py +0 -415
  36. fractal_server/app/runner/v1/_common.py +0 -620
  37. fractal_server/app/runner/v1/_local/__init__.py +0 -186
  38. fractal_server/app/runner/v1/_local/_local_config.py +0 -105
  39. fractal_server/app/runner/v1/_local/_submit_setup.py +0 -48
  40. fractal_server/app/runner/v1/_local/executor.py +0 -100
  41. fractal_server/app/runner/v1/_slurm/__init__.py +0 -312
  42. fractal_server/app/runner/v1/_slurm/_submit_setup.py +0 -81
  43. fractal_server/app/runner/v1/_slurm/get_slurm_config.py +0 -163
  44. fractal_server/app/runner/v1/common.py +0 -117
  45. fractal_server/app/runner/v1/handle_failed_job.py +0 -141
  46. fractal_server/app/schemas/v1/__init__.py +0 -37
  47. fractal_server/app/schemas/v1/applyworkflow.py +0 -161
  48. fractal_server/app/schemas/v1/dataset.py +0 -165
  49. fractal_server/app/schemas/v1/dumps.py +0 -64
  50. fractal_server/app/schemas/v1/manifest.py +0 -126
  51. fractal_server/app/schemas/v1/project.py +0 -66
  52. fractal_server/app/schemas/v1/state.py +0 -18
  53. fractal_server/app/schemas/v1/task.py +0 -167
  54. fractal_server/app/schemas/v1/task_collection.py +0 -110
  55. fractal_server/app/schemas/v1/workflow.py +0 -212
  56. fractal_server/data_migrations/2_11_0.py +0 -168
  57. fractal_server/tasks/v1/_TaskCollectPip.py +0 -103
  58. fractal_server/tasks/v1/__init__.py +0 -0
  59. fractal_server/tasks/v1/background_operations.py +0 -352
  60. fractal_server/tasks/v1/endpoint_operations.py +0 -156
  61. fractal_server/tasks/v1/get_collection_data.py +0 -14
  62. fractal_server/tasks/v1/utils.py +0 -67
  63. {fractal_server-2.11.0a10.dist-info → fractal_server-2.12.0a0.dist-info}/LICENSE +0 -0
  64. {fractal_server-2.11.0a10.dist-info → fractal_server-2.12.0a0.dist-info}/WHEEL +0 -0
  65. {fractal_server-2.11.0a10.dist-info → fractal_server-2.12.0a0.dist-info}/entry_points.txt +0 -0
@@ -1,415 +0,0 @@
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
- Runner backend subsystem root
15
-
16
- This module is the single entry point to the runner backend subsystem. Other
17
- subystems should only import this module and not its submodules or the
18
- individual backends.
19
- """
20
- import os
21
- import traceback
22
- from pathlib import Path
23
- from typing import Optional
24
-
25
- from sqlalchemy.orm import Session as DBSyncSession
26
-
27
- from ....logger import get_logger
28
- from ....logger import reset_logger_handlers
29
- from ....logger import set_logger
30
- from ....syringe import Inject
31
- from ....utils import get_timestamp
32
- from ...db import DB
33
- from ...models.v1 import ApplyWorkflow
34
- from ...models.v1 import Dataset
35
- from ...models.v1 import Workflow
36
- from ...models.v1 import WorkflowTask
37
- from ...schemas.v1 import JobStatusTypeV1
38
- from ..exceptions import JobExecutionError
39
- from ..exceptions import TaskExecutionError
40
- from ..executors.slurm.sudo._subprocess_run_as_user import (
41
- _mkdir_as_user,
42
- )
43
- from ..filenames import WORKFLOW_LOG_FILENAME
44
- from ..task_files import task_subfolder_name
45
- from ._local import process_workflow as local_process_workflow
46
- from ._slurm import process_workflow as slurm_process_workflow
47
- from .common import close_job_logger
48
- from .common import validate_workflow_compatibility # noqa: F401
49
- from .handle_failed_job import assemble_history_failed_job
50
- from .handle_failed_job import assemble_meta_failed_job
51
- from fractal_server import __VERSION__
52
- from fractal_server.config import get_settings
53
-
54
-
55
- _backends = {}
56
- _backends["local"] = local_process_workflow
57
- _backends["slurm"] = slurm_process_workflow
58
-
59
-
60
- def fail_job(
61
- *,
62
- db: DBSyncSession,
63
- job: ApplyWorkflow,
64
- log_msg: str,
65
- logger_name: str,
66
- emit_log: bool = False,
67
- ) -> None:
68
- logger = get_logger(logger_name=logger_name)
69
- if emit_log:
70
- logger.error(log_msg)
71
- reset_logger_handlers(logger)
72
- job.status = JobStatusTypeV1.FAILED
73
- job.end_timestamp = get_timestamp()
74
- job.log = log_msg
75
- db.merge(job)
76
- db.commit()
77
- db.close()
78
- return
79
-
80
-
81
- async def submit_workflow(
82
- *,
83
- workflow_id: int,
84
- input_dataset_id: int,
85
- output_dataset_id: int,
86
- job_id: int,
87
- worker_init: Optional[str] = None,
88
- slurm_user: Optional[str] = None,
89
- user_cache_dir: Optional[str] = None,
90
- ) -> None:
91
- """
92
- Prepares a workflow and applies it to a dataset
93
-
94
- This function wraps the process_workflow one, which is different for each
95
- backend (e.g. local or slurm backend).
96
-
97
- Args:
98
- workflow_id:
99
- ID of the workflow being applied
100
- input_dataset_id:
101
- Input dataset ID
102
- output_dataset_id:
103
- ID of the destination dataset of the workflow.
104
- job_id:
105
- Id of the job record which stores the state for the current
106
- workflow application.
107
- worker_init:
108
- Custom executor parameters that get parsed before the execution of
109
- each task.
110
- user_cache_dir:
111
- Cache directory (namely a path where the user can write); for the
112
- slurm backend, this is used as a base directory for
113
- `job.working_dir_user`.
114
- slurm_user:
115
- The username to impersonate for the workflow execution, for the
116
- slurm backend.
117
- """
118
-
119
- logger_name = f"WF{workflow_id}_job{job_id}"
120
- logger = set_logger(logger_name=logger_name)
121
-
122
- with next(DB.get_sync_db()) as db_sync:
123
-
124
- job: ApplyWorkflow = db_sync.get(ApplyWorkflow, job_id)
125
- if not job:
126
- logger.error(f"ApplyWorkflow {job_id} does not exist")
127
- return
128
-
129
- settings = Inject(get_settings)
130
- FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
131
- if FRACTAL_RUNNER_BACKEND == "local":
132
- process_workflow = local_process_workflow
133
- elif FRACTAL_RUNNER_BACKEND == "slurm":
134
- process_workflow = slurm_process_workflow
135
- else:
136
-
137
- if FRACTAL_RUNNER_BACKEND == "local_experimental":
138
- log_msg = (
139
- f"{FRACTAL_RUNNER_BACKEND=} is not available for v1 jobs."
140
- )
141
- else:
142
- log_msg = f"Invalid {FRACTAL_RUNNER_BACKEND=}"
143
-
144
- fail_job(
145
- job=job,
146
- db=db_sync,
147
- log_msg=log_msg,
148
- logger_name=logger_name,
149
- emit_log=True,
150
- )
151
- return
152
-
153
- # Declare runner backend and set `process_workflow` function
154
-
155
- input_dataset: Dataset = db_sync.get(Dataset, input_dataset_id)
156
- output_dataset: Dataset = db_sync.get(Dataset, output_dataset_id)
157
- workflow: Workflow = db_sync.get(Workflow, workflow_id)
158
- if not (input_dataset and output_dataset and workflow):
159
- log_msg = ""
160
- if not input_dataset:
161
- log_msg += (
162
- f"Cannot fetch input_dataset {input_dataset_id} "
163
- "from database\n"
164
- )
165
- if not output_dataset:
166
- log_msg += (
167
- f"Cannot fetch output_dataset {output_dataset_id} "
168
- "from database\n"
169
- )
170
- if not workflow:
171
- log_msg += (
172
- f"Cannot fetch workflow {workflow_id} from database\n"
173
- )
174
- fail_job(
175
- db=db_sync, job=job, log_msg=log_msg, logger_name=logger_name
176
- )
177
- return
178
-
179
- # Prepare some of process_workflow arguments
180
- input_paths = input_dataset.paths
181
- output_path = output_dataset.paths[0]
182
-
183
- # Define and create server-side working folder
184
- project_id = workflow.project_id
185
- timestamp_string = get_timestamp().strftime("%Y%m%d_%H%M%S")
186
- WORKFLOW_DIR_LOCAL = settings.FRACTAL_RUNNER_WORKING_BASE_DIR / (
187
- f"proj_{project_id:07d}_wf_{workflow_id:07d}_job_{job_id:07d}"
188
- f"_{timestamp_string}"
189
- )
190
-
191
- if WORKFLOW_DIR_LOCAL.exists():
192
- fail_job(
193
- db=db_sync,
194
- job=job,
195
- log_msg=f"Workflow dir {WORKFLOW_DIR_LOCAL} already exists.",
196
- logger_name=logger_name,
197
- emit_log=True,
198
- )
199
- return
200
-
201
- # Create WORKFLOW_DIR
202
- original_umask = os.umask(0)
203
- WORKFLOW_DIR_LOCAL.mkdir(parents=True, mode=0o755)
204
- os.umask(original_umask)
205
-
206
- # Define and create WORKFLOW_DIR_REMOTE
207
- if FRACTAL_RUNNER_BACKEND == "local":
208
- WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
209
- elif FRACTAL_RUNNER_BACKEND == "slurm":
210
- WORKFLOW_DIR_REMOTE = (
211
- Path(user_cache_dir) / WORKFLOW_DIR_LOCAL.name
212
- )
213
- _mkdir_as_user(folder=str(WORKFLOW_DIR_REMOTE), user=slurm_user)
214
-
215
- # Create all tasks subfolders
216
- for order in range(job.first_task_index, job.last_task_index + 1):
217
- subfolder_name = task_subfolder_name(
218
- order=order,
219
- task_name=workflow.task_list[order].task.name,
220
- )
221
- original_umask = os.umask(0)
222
- (WORKFLOW_DIR_LOCAL / subfolder_name).mkdir(mode=0o755)
223
- os.umask(original_umask)
224
- if FRACTAL_RUNNER_BACKEND == "slurm":
225
- _mkdir_as_user(
226
- folder=str(WORKFLOW_DIR_REMOTE / subfolder_name),
227
- user=slurm_user,
228
- )
229
-
230
- # Update db
231
- job.working_dir = WORKFLOW_DIR_LOCAL.as_posix()
232
- job.working_dir_user = WORKFLOW_DIR_REMOTE.as_posix()
233
- db_sync.merge(job)
234
- db_sync.commit()
235
-
236
- # After Session.commit() is called, either explicitly or when using a
237
- # context manager, all objects associated with the Session are expired.
238
- # https://docs.sqlalchemy.org/en/14/orm/
239
- # session_basics.html#opening-and-closing-a-session
240
- # https://docs.sqlalchemy.org/en/14/orm/
241
- # session_state_management.html#refreshing-expiring
242
-
243
- # See issue #928:
244
- # https://github.com/fractal-analytics-platform/
245
- # fractal-server/issues/928
246
-
247
- db_sync.refresh(input_dataset)
248
- db_sync.refresh(output_dataset)
249
- db_sync.refresh(workflow)
250
-
251
- # Write logs
252
- log_file_path = WORKFLOW_DIR_LOCAL / WORKFLOW_LOG_FILENAME
253
- logger = set_logger(
254
- logger_name=logger_name,
255
- log_file_path=log_file_path,
256
- )
257
- logger.info(
258
- f'Start execution of workflow "{workflow.name}"; '
259
- f"more logs at {str(log_file_path)}"
260
- )
261
- logger.debug(f"fractal_server.__VERSION__: {__VERSION__}")
262
- logger.debug(f"FRACTAL_RUNNER_BACKEND: {FRACTAL_RUNNER_BACKEND}")
263
- logger.debug(f"slurm_user: {slurm_user}")
264
- logger.debug(f"slurm_account: {job.slurm_account}")
265
- logger.debug(f"worker_init: {worker_init}")
266
- logger.debug(f"input metadata keys: {list(input_dataset.meta.keys())}")
267
- logger.debug(f"input_paths: {input_paths}")
268
- logger.debug(f"output_path: {output_path}")
269
- logger.debug(f"job.id: {job.id}")
270
- logger.debug(f"job.working_dir: {job.working_dir}")
271
- logger.debug(f"job.working_dir_user: {job.working_dir_user}")
272
- logger.debug(f"job.first_task_index: {job.first_task_index}")
273
- logger.debug(f"job.last_task_index: {job.last_task_index}")
274
- logger.debug(f'START workflow "{workflow.name}"')
275
-
276
- try:
277
- # "The Session.close() method does not prevent the Session from being
278
- # used again. The Session itself does not actually have a distinct
279
- # “closed” state; it merely means the Session will release all database
280
- # connections and ORM objects."
281
- # (https://docs.sqlalchemy.org/en/20/orm/session_api.html#sqlalchemy.orm.Session.close).
282
- #
283
- # We close the session before the (possibly long) process_workflow
284
- # call, to make sure all DB connections are released. The reason why we
285
- # are not using a context manager within the try block is that we also
286
- # need access to db_sync in the except branches.
287
- db_sync = next(DB.get_sync_db())
288
- db_sync.close()
289
-
290
- output_dataset_meta_hist = await process_workflow(
291
- workflow=workflow,
292
- input_paths=input_paths,
293
- output_path=output_path,
294
- input_metadata=input_dataset.meta,
295
- input_history=input_dataset.history,
296
- slurm_user=slurm_user,
297
- slurm_account=job.slurm_account,
298
- user_cache_dir=user_cache_dir,
299
- workflow_dir_local=WORKFLOW_DIR_LOCAL,
300
- workflow_dir_remote=WORKFLOW_DIR_REMOTE,
301
- logger_name=logger_name,
302
- worker_init=worker_init,
303
- first_task_index=job.first_task_index,
304
- last_task_index=job.last_task_index,
305
- )
306
-
307
- logger.info(
308
- f'End execution of workflow "{workflow.name}"; '
309
- f"more logs at {str(log_file_path)}"
310
- )
311
- logger.debug(f'END workflow "{workflow.name}"')
312
-
313
- # Replace output_dataset.meta and output_dataset.history with their
314
- # up-to-date versions, obtained within process_workflow
315
- output_dataset.history = output_dataset_meta_hist.pop("history")
316
- output_dataset.meta = output_dataset_meta_hist.pop("metadata")
317
-
318
- db_sync.merge(output_dataset)
319
-
320
- # Update job DB entry
321
- job.status = JobStatusTypeV1.DONE
322
- job.end_timestamp = get_timestamp()
323
- with log_file_path.open("r") as f:
324
- logs = f.read()
325
- job.log = logs
326
- db_sync.merge(job)
327
- close_job_logger(logger)
328
- db_sync.commit()
329
-
330
- except TaskExecutionError as e:
331
-
332
- logger.debug(f'FAILED workflow "{workflow.name}", TaskExecutionError.')
333
- logger.info(f'Workflow "{workflow.name}" failed (TaskExecutionError).')
334
-
335
- # Assemble output_dataset.meta based on the last successful task, i.e.
336
- # based on METADATA_FILENAME
337
- output_dataset.meta = assemble_meta_failed_job(job, output_dataset)
338
-
339
- # Assemble new history and assign it to output_dataset.meta
340
- failed_wftask = db_sync.get(WorkflowTask, e.workflow_task_id)
341
- output_dataset.history = assemble_history_failed_job(
342
- job,
343
- output_dataset,
344
- workflow,
345
- logger,
346
- failed_wftask=failed_wftask,
347
- )
348
-
349
- db_sync.merge(output_dataset)
350
-
351
- exception_args_string = "\n".join(e.args)
352
- log_msg = (
353
- f"TASK ERROR: "
354
- f"Task name: {e.task_name}, "
355
- f"position in Workflow: {e.workflow_task_order}\n"
356
- f"TRACEBACK:\n{exception_args_string}"
357
- )
358
- fail_job(db=db_sync, job=job, log_msg=log_msg, logger_name=logger_name)
359
-
360
- except JobExecutionError as e:
361
-
362
- logger.debug(f'FAILED workflow "{workflow.name}", JobExecutionError.')
363
- logger.info(f'Workflow "{workflow.name}" failed (JobExecutionError).')
364
-
365
- # Assemble output_dataset.meta based on the last successful task, i.e.
366
- # based on METADATA_FILENAME
367
- output_dataset.meta = assemble_meta_failed_job(job, output_dataset)
368
-
369
- # Assemble new history and assign it to output_dataset.meta
370
- output_dataset.history = assemble_history_failed_job(
371
- job,
372
- output_dataset,
373
- workflow,
374
- logger,
375
- )
376
-
377
- db_sync.merge(output_dataset)
378
- error = e.assemble_error()
379
- fail_job(
380
- db=db_sync,
381
- job=job,
382
- log_msg=f"JOB ERROR in Fractal job {job.id}:\nTRACEBACK:\n{error}",
383
- logger_name=logger_name,
384
- )
385
-
386
- except Exception:
387
-
388
- logger.debug(f'FAILED workflow "{workflow.name}", unknown error.')
389
- logger.info(f'Workflow "{workflow.name}" failed (unkwnon error).')
390
-
391
- current_traceback = traceback.format_exc()
392
-
393
- # Assemble output_dataset.meta based on the last successful task, i.e.
394
- # based on METADATA_FILENAME
395
- output_dataset.meta = assemble_meta_failed_job(job, output_dataset)
396
-
397
- # Assemble new history and assign it to output_dataset.meta
398
- output_dataset.history = assemble_history_failed_job(
399
- job,
400
- output_dataset,
401
- workflow,
402
- logger,
403
- )
404
-
405
- db_sync.merge(output_dataset)
406
-
407
- log_msg = (
408
- f"UNKNOWN ERROR in Fractal job {job.id}\n"
409
- f"TRACEBACK:\n{current_traceback}"
410
- )
411
- fail_job(db=db_sync, job=job, log_msg=log_msg, logger_name=logger_name)
412
-
413
- finally:
414
- db_sync.close()
415
- reset_logger_handlers(logger)