fractal-server 2.0.6__py3-none-any.whl → 2.2.0__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 (49) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/db/__init__.py +1 -1
  3. fractal_server/app/routes/admin/v1.py +2 -4
  4. fractal_server/app/routes/admin/v2.py +2 -4
  5. fractal_server/app/routes/api/v1/_aux_functions.py +24 -0
  6. fractal_server/app/routes/api/v1/job.py +3 -4
  7. fractal_server/app/routes/api/v1/project.py +28 -18
  8. fractal_server/app/routes/api/v2/_aux_functions.py +35 -12
  9. fractal_server/app/routes/api/v2/job.py +3 -4
  10. fractal_server/app/routes/api/v2/project.py +21 -0
  11. fractal_server/app/routes/api/v2/submit.py +36 -15
  12. fractal_server/app/routes/aux/_job.py +3 -1
  13. fractal_server/app/routes/aux/_runner.py +3 -3
  14. fractal_server/app/runner/executors/slurm/executor.py +169 -68
  15. fractal_server/app/runner/shutdown.py +88 -0
  16. fractal_server/app/runner/task_files.py +59 -27
  17. fractal_server/app/runner/v1/__init__.py +113 -64
  18. fractal_server/app/runner/v1/_common.py +53 -51
  19. fractal_server/app/runner/v1/_local/__init__.py +12 -11
  20. fractal_server/app/runner/v1/_local/_submit_setup.py +4 -4
  21. fractal_server/app/runner/v1/_slurm/__init__.py +16 -16
  22. fractal_server/app/runner/v1/_slurm/_submit_setup.py +11 -10
  23. fractal_server/app/runner/v1/_slurm/get_slurm_config.py +6 -6
  24. fractal_server/app/runner/v2/__init__.py +139 -60
  25. fractal_server/app/runner/v2/_local/__init__.py +12 -11
  26. fractal_server/app/runner/v2/_local/_local_config.py +1 -1
  27. fractal_server/app/runner/v2/_local/_submit_setup.py +4 -4
  28. fractal_server/app/runner/v2/_local_experimental/__init__.py +155 -0
  29. fractal_server/app/runner/v2/_local_experimental/_local_config.py +108 -0
  30. fractal_server/app/runner/v2/_local_experimental/_submit_setup.py +42 -0
  31. fractal_server/app/runner/v2/_local_experimental/executor.py +156 -0
  32. fractal_server/app/runner/v2/_slurm/__init__.py +10 -10
  33. fractal_server/app/runner/v2/_slurm/_submit_setup.py +11 -10
  34. fractal_server/app/runner/v2/_slurm/get_slurm_config.py +6 -6
  35. fractal_server/app/runner/v2/runner.py +17 -15
  36. fractal_server/app/runner/v2/runner_functions.py +38 -38
  37. fractal_server/app/runner/v2/runner_functions_low_level.py +12 -6
  38. fractal_server/app/security/__init__.py +4 -5
  39. fractal_server/config.py +73 -19
  40. fractal_server/gunicorn_fractal.py +40 -0
  41. fractal_server/{logger/__init__.py → logger.py} +2 -2
  42. fractal_server/main.py +45 -26
  43. fractal_server/migrations/env.py +1 -1
  44. {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/METADATA +4 -1
  45. {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/RECORD +48 -43
  46. fractal_server/logger/gunicorn_logger.py +0 -19
  47. {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/LICENSE +0 -0
  48. {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/WHEEL +0 -0
  49. {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/entry_points.txt +0 -0
@@ -1 +1 @@
1
- __VERSION__ = "2.0.6"
1
+ __VERSION__ = "2.2.0"
@@ -63,7 +63,7 @@ class DB:
63
63
  }
64
64
 
65
65
  cls._engine_async = create_async_engine(
66
- settings.DATABASE_URL,
66
+ settings.DATABASE_ASYNC_URL,
67
67
  echo=settings.DB_ECHO,
68
68
  future=True,
69
69
  **engine_kwargs_async,
@@ -35,7 +35,7 @@ from ...schemas.v1 import WorkflowReadV1
35
35
  from ...security import current_active_superuser
36
36
  from ..aux._job import _write_shutdown_file
37
37
  from ..aux._job import _zip_folder_to_byte_stream
38
- from ..aux._runner import _check_backend_is_slurm
38
+ from ..aux._runner import _is_shutdown_available
39
39
 
40
40
  router_admin_v1 = APIRouter()
41
41
 
@@ -349,11 +349,9 @@ async def stop_job(
349
349
  ) -> Response:
350
350
  """
351
351
  Stop execution of a workflow job.
352
-
353
- Only available for slurm backend.
354
352
  """
355
353
 
356
- _check_backend_is_slurm()
354
+ _is_shutdown_available()
357
355
 
358
356
  job = await db.get(ApplyWorkflow, job_id)
359
357
  if job is None:
@@ -38,7 +38,7 @@ from ...schemas.v2 import ProjectReadV2
38
38
  from ...security import current_active_superuser
39
39
  from ..aux._job import _write_shutdown_file
40
40
  from ..aux._job import _zip_folder_to_byte_stream
41
- from ..aux._runner import _check_backend_is_slurm
41
+ from ..aux._runner import _is_shutdown_available
42
42
 
43
43
  router_admin_v2 = APIRouter()
44
44
 
@@ -236,11 +236,9 @@ async def stop_job(
236
236
  ) -> Response:
237
237
  """
238
238
  Stop execution of a workflow job.
239
-
240
- Only available for slurm backend.
241
239
  """
242
240
 
243
- _check_backend_is_slurm()
241
+ _is_shutdown_available()
244
242
 
245
243
  job = await db.get(JobV2, job_id)
246
244
  if job is None:
@@ -436,3 +436,27 @@ async def _workflow_insert_task(
436
436
  await db.refresh(wf_task)
437
437
 
438
438
  return wf_task
439
+
440
+
441
+ async def clean_app_job_list_v1(
442
+ db: AsyncSession, jobs_list: list[int]
443
+ ) -> list[int]:
444
+ """
445
+ Remove from a job list all jobs with status different from submitted.
446
+
447
+ Args:
448
+ db: Async database session
449
+ jobs_list: List of job IDs currently associated to the app.
450
+
451
+ Return:
452
+ List of IDs for submitted jobs.
453
+ """
454
+ stmt = select(ApplyWorkflow).where(ApplyWorkflow.id.in_(jobs_list))
455
+ result = await db.execute(stmt)
456
+ db_jobs_list = result.scalars().all()
457
+ submitted_job_ids = [
458
+ job.id
459
+ for job in db_jobs_list
460
+ if job.status == JobStatusTypeV1.SUBMITTED
461
+ ]
462
+ return submitted_job_ids
@@ -19,7 +19,7 @@ from ....security import current_active_user
19
19
  from ....security import User
20
20
  from ...aux._job import _write_shutdown_file
21
21
  from ...aux._job import _zip_folder_to_byte_stream
22
- from ...aux._runner import _check_backend_is_slurm
22
+ from ...aux._runner import _is_shutdown_available
23
23
  from ._aux_functions import _get_job_check_owner
24
24
  from ._aux_functions import _get_project_check_owner
25
25
  from ._aux_functions import _get_workflow_check_owner
@@ -177,11 +177,10 @@ async def stop_job(
177
177
  db: AsyncSession = Depends(get_async_db),
178
178
  ) -> Response:
179
179
  """
180
- Stop execution of a workflow job (only available for slurm backend)
180
+ Stop execution of a workflow job.
181
181
  """
182
182
 
183
- # This endpoint is only implemented for SLURM backend
184
- _check_backend_is_slurm()
183
+ _is_shutdown_available()
185
184
 
186
185
  # Get job from DB
187
186
  output = await _get_job_check_owner(
@@ -1,3 +1,4 @@
1
+ import os
1
2
  from datetime import datetime
2
3
  from datetime import timedelta
3
4
  from datetime import timezone
@@ -7,13 +8,12 @@ from fastapi import APIRouter
7
8
  from fastapi import BackgroundTasks
8
9
  from fastapi import Depends
9
10
  from fastapi import HTTPException
11
+ from fastapi import Request
10
12
  from fastapi import Response
11
13
  from fastapi import status
12
- from sqlalchemy.exc import IntegrityError
13
14
  from sqlmodel import select
14
15
 
15
16
  from .....config import get_settings
16
- from .....logger import close_logger
17
17
  from .....logger import set_logger
18
18
  from .....syringe import Inject
19
19
  from ....db import AsyncSession
@@ -42,8 +42,10 @@ from ._aux_functions import _get_dataset_check_owner
42
42
  from ._aux_functions import _get_project_check_owner
43
43
  from ._aux_functions import _get_submitted_jobs_statement
44
44
  from ._aux_functions import _get_workflow_check_owner
45
+ from ._aux_functions import clean_app_job_list_v1
45
46
 
46
47
  router = APIRouter()
48
+ logger = set_logger(__name__)
47
49
 
48
50
 
49
51
  def _encode_as_utc(dt: datetime):
@@ -86,20 +88,11 @@ async def create_project(
86
88
 
87
89
  db_project = Project(**project.dict())
88
90
  db_project.user_list.append(user)
89
- try:
90
- db.add(db_project)
91
- await db.commit()
92
- await db.refresh(db_project)
93
- await db.close()
94
- except IntegrityError as e:
95
- await db.rollback()
96
- logger = set_logger("create_project")
97
- logger.error(str(e))
98
- close_logger(logger)
99
- raise HTTPException(
100
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
101
- detail=str(e),
102
- )
91
+
92
+ db.add(db_project)
93
+ await db.commit()
94
+ await db.refresh(db_project)
95
+ await db.close()
103
96
 
104
97
  return db_project
105
98
 
@@ -252,10 +245,23 @@ async def apply_workflow(
252
245
  background_tasks: BackgroundTasks,
253
246
  input_dataset_id: int,
254
247
  output_dataset_id: int,
248
+ request: Request,
255
249
  user: User = Depends(current_active_verified_user),
256
250
  db: AsyncSession = Depends(get_async_db),
257
251
  ) -> Optional[ApplyWorkflowReadV1]:
258
252
 
253
+ # Remove non-submitted V1 jobs from the app state when the list grows
254
+ # beyond a threshold
255
+ settings = Inject(get_settings)
256
+ if (
257
+ len(request.app.state.jobsV1)
258
+ > settings.FRACTAL_API_MAX_JOB_LIST_LENGTH
259
+ ):
260
+ new_jobs_list = await clean_app_job_list_v1(
261
+ db, request.app.state.jobsV1
262
+ )
263
+ request.app.state.jobsV1 = new_jobs_list
264
+
259
265
  output = await _get_dataset_check_owner(
260
266
  project_id=project_id,
261
267
  dataset_id=input_dataset_id,
@@ -312,7 +318,6 @@ async def apply_workflow(
312
318
  )
313
319
 
314
320
  # If backend is SLURM, check that the user has required attributes
315
- settings = Inject(get_settings)
316
321
  backend = settings.FRACTAL_RUNNER_BACKEND
317
322
  if backend == "slurm":
318
323
  if not user.slurm_user:
@@ -474,7 +479,12 @@ async def apply_workflow(
474
479
  slurm_user=user.slurm_user,
475
480
  user_cache_dir=user.cache_dir,
476
481
  )
477
-
482
+ request.app.state.jobsV1.append(job.id)
483
+ logger.info(
484
+ f"Current worker's pid is {os.getpid()}. "
485
+ f"Current status of worker job's list "
486
+ f"{request.app.state.jobsV1}"
487
+ )
478
488
  await db.close()
479
489
 
480
490
  return job
@@ -95,8 +95,11 @@ async def _get_workflow_check_owner(
95
95
  project = await _get_project_check_owner(
96
96
  project_id=project_id, user_id=user_id, db=db
97
97
  )
98
+
98
99
  # Get workflow
99
- workflow = await db.get(WorkflowV2, workflow_id)
100
+ # (See issue 1087 for 'populate_existing=True')
101
+ workflow = await db.get(WorkflowV2, workflow_id, populate_existing=True)
102
+
100
103
  if not workflow:
101
104
  raise HTTPException(
102
105
  status_code=status.HTTP_404_NOT_FOUND, detail="Workflow not found"
@@ -107,10 +110,6 @@ async def _get_workflow_check_owner(
107
110
  detail=(f"Invalid {project_id=} for {workflow_id=}."),
108
111
  )
109
112
 
110
- # Refresh so that workflow.project relationship is loaded (see discussion
111
- # in issue #1063)
112
- await db.refresh(workflow)
113
-
114
113
  return workflow
115
114
 
116
115
 
@@ -255,13 +254,15 @@ async def _get_dataset_check_owner(
255
254
  HTTPException(status_code=422_UNPROCESSABLE_ENTITY):
256
255
  If the dataset is not associated to the project
257
256
  """
258
-
259
257
  # Access control for project
260
258
  project = await _get_project_check_owner(
261
259
  project_id=project_id, user_id=user_id, db=db
262
260
  )
261
+
263
262
  # Get dataset
264
- dataset = await db.get(DatasetV2, dataset_id)
263
+ # (See issue 1087 for 'populate_existing=True')
264
+ dataset = await db.get(DatasetV2, dataset_id, populate_existing=True)
265
+
265
266
  if not dataset:
266
267
  raise HTTPException(
267
268
  status_code=status.HTTP_404_NOT_FOUND, detail="Dataset not found"
@@ -272,10 +273,6 @@ async def _get_dataset_check_owner(
272
273
  detail=f"Invalid {project_id=} for {dataset_id=}",
273
274
  )
274
275
 
275
- # Refresh so that dataset.project relationship is loaded (see discussion
276
- # in issue #1063)
277
- await db.refresh(dataset)
278
-
279
276
  return dict(dataset=dataset, project=project)
280
277
 
281
278
 
@@ -497,6 +494,32 @@ async def _workflow_insert_task(
497
494
  db_workflow.task_list.reorder() # type: ignore
498
495
  flag_modified(db_workflow, "task_list")
499
496
  await db.commit()
500
- await db.refresh(wf_task)
497
+
498
+ # See issue 1087 for 'populate_existing=True'
499
+ wf_task = await db.get(WorkflowTaskV2, wf_task.id, populate_existing=True)
501
500
 
502
501
  return wf_task
502
+
503
+
504
+ async def clean_app_job_list_v2(
505
+ db: AsyncSession, jobs_list: list[int]
506
+ ) -> list[int]:
507
+ """
508
+ Remove from a job list all jobs with status different from submitted.
509
+
510
+ Args:
511
+ db: Async database session
512
+ jobs_list: List of job IDs currently associated to the app.
513
+
514
+ Return:
515
+ List of IDs for submitted jobs.
516
+ """
517
+ stmt = select(JobV2).where(JobV2.id.in_(jobs_list))
518
+ result = await db.execute(stmt)
519
+ db_jobs_list = result.scalars().all()
520
+ submitted_job_ids = [
521
+ job.id
522
+ for job in db_jobs_list
523
+ if job.status == JobStatusTypeV2.SUBMITTED
524
+ ]
525
+ return submitted_job_ids
@@ -19,7 +19,7 @@ from ....security import current_active_user
19
19
  from ....security import User
20
20
  from ...aux._job import _write_shutdown_file
21
21
  from ...aux._job import _zip_folder_to_byte_stream
22
- from ...aux._runner import _check_backend_is_slurm
22
+ from ...aux._runner import _is_shutdown_available
23
23
  from ._aux_functions import _get_job_check_owner
24
24
  from ._aux_functions import _get_project_check_owner
25
25
  from ._aux_functions import _get_workflow_check_owner
@@ -180,11 +180,10 @@ async def stop_job(
180
180
  db: AsyncSession = Depends(get_async_db),
181
181
  ) -> Response:
182
182
  """
183
- Stop execution of a workflow job (only available for slurm backend)
183
+ Stop execution of a workflow job.
184
184
  """
185
185
 
186
- # This endpoint is only implemented for SLURM backend
187
- _check_backend_is_slurm()
186
+ _is_shutdown_available()
188
187
 
189
188
  # Get job from DB
190
189
  output = await _get_job_check_owner(
@@ -7,6 +7,8 @@ from fastapi import Response
7
7
  from fastapi import status
8
8
  from sqlmodel import select
9
9
 
10
+ from .....logger import reset_logger_handlers
11
+ from .....logger import set_logger
10
12
  from ....db import AsyncSession
11
13
  from ....db import get_async_db
12
14
  from ....models.v2 import DatasetV2
@@ -122,9 +124,11 @@ async def delete_project(
122
124
  """
123
125
  Delete project
124
126
  """
127
+
125
128
  project = await _get_project_check_owner(
126
129
  project_id=project_id, user_id=user.id, db=db
127
130
  )
131
+ logger = set_logger(__name__)
128
132
 
129
133
  # Fail if there exist jobs that are submitted and in relation with the
130
134
  # current project.
@@ -147,6 +151,7 @@ async def delete_project(
147
151
  stm = select(WorkflowV2).where(WorkflowV2.project_id == project_id)
148
152
  res = await db.execute(stm)
149
153
  workflows = res.scalars().all()
154
+ logger.info("Start of cascade operations on Workflows.")
150
155
  for wf in workflows:
151
156
  # Cascade operations: set foreign-keys to null for jobs which are in
152
157
  # relationship with the current workflow
@@ -154,14 +159,18 @@ async def delete_project(
154
159
  res = await db.execute(stm)
155
160
  jobs = res.scalars().all()
156
161
  for job in jobs:
162
+ logger.info(f"Setting Job[{job.id}].workflow_id to None.")
157
163
  job.workflow_id = None
158
164
  # Delete workflow
165
+ logger.info(f"Adding Workflow[{wf.id}] to deletion.")
159
166
  await db.delete(wf)
167
+ logger.info("End of cascade operations on Workflows.")
160
168
 
161
169
  # Dataset
162
170
  stm = select(DatasetV2).where(DatasetV2.project_id == project_id)
163
171
  res = await db.execute(stm)
164
172
  datasets = res.scalars().all()
173
+ logger.info("Start of cascade operations on Datasets.")
165
174
  for ds in datasets:
166
175
  # Cascade operations: set foreign-keys to null for jobs which are in
167
176
  # relationship with the current dataset
@@ -169,18 +178,30 @@ async def delete_project(
169
178
  res = await db.execute(stm)
170
179
  jobs = res.scalars().all()
171
180
  for job in jobs:
181
+ logger.info(f"Setting Job[{job.id}].dataset_id to None.")
172
182
  job.dataset_id = None
173
183
  # Delete dataset
184
+ logger.info(f"Adding Dataset[{ds.id}] to deletion.")
174
185
  await db.delete(ds)
186
+ logger.info("End of cascade operations on Datasets.")
175
187
 
176
188
  # Job
189
+ logger.info("Start of cascade operations on Jobs.")
177
190
  stm = select(JobV2).where(JobV2.project_id == project_id)
178
191
  res = await db.execute(stm)
179
192
  jobs = res.scalars().all()
180
193
  for job in jobs:
194
+ logger.info(f"Setting Job[{job.id}].project_id to None.")
181
195
  job.project_id = None
196
+ logger.info("End of cascade operations on Jobs.")
182
197
 
198
+ logger.info(f"Adding Project[{project.id}] to deletion.")
183
199
  await db.delete(project)
200
+
201
+ logger.info("Committing changes to db...")
184
202
  await db.commit()
185
203
 
204
+ logger.info("Everything has been deleted correctly.")
205
+ reset_logger_handlers(logger)
206
+
186
207
  return Response(status_code=status.HTTP_204_NO_CONTENT)
@@ -1,3 +1,4 @@
1
+ import os
1
2
  from datetime import datetime
2
3
  from datetime import timedelta
3
4
  from datetime import timezone
@@ -8,10 +9,12 @@ from fastapi import APIRouter
8
9
  from fastapi import BackgroundTasks
9
10
  from fastapi import Depends
10
11
  from fastapi import HTTPException
12
+ from fastapi import Request
11
13
  from fastapi import status
12
14
  from sqlmodel import select
13
15
 
14
16
  from .....config import get_settings
17
+ from .....logger import set_logger
15
18
  from .....syringe import Inject
16
19
  from .....utils import get_timestamp
17
20
  from ....db import AsyncSession
@@ -28,6 +31,7 @@ from ....security import current_active_verified_user
28
31
  from ....security import User
29
32
  from ._aux_functions import _get_dataset_check_owner
30
33
  from ._aux_functions import _get_workflow_check_owner
34
+ from ._aux_functions import clean_app_job_list_v2
31
35
 
32
36
 
33
37
  def _encode_as_utc(dt: datetime):
@@ -35,6 +39,7 @@ def _encode_as_utc(dt: datetime):
35
39
 
36
40
 
37
41
  router = APIRouter()
42
+ logger = set_logger(__name__)
38
43
 
39
44
 
40
45
  @router.post(
@@ -48,10 +53,23 @@ async def apply_workflow(
48
53
  dataset_id: int,
49
54
  job_create: JobCreateV2,
50
55
  background_tasks: BackgroundTasks,
56
+ request: Request,
51
57
  user: User = Depends(current_active_verified_user),
52
58
  db: AsyncSession = Depends(get_async_db),
53
59
  ) -> Optional[JobReadV2]:
54
60
 
61
+ # Remove non-submitted V2 jobs from the app state when the list grows
62
+ # beyond a threshold
63
+ settings = Inject(get_settings)
64
+ if (
65
+ len(request.app.state.jobsV2)
66
+ > settings.FRACTAL_API_MAX_JOB_LIST_LENGTH
67
+ ):
68
+ new_jobs_list = await clean_app_job_list_v2(
69
+ db, request.app.state.jobsV2
70
+ )
71
+ request.app.state.jobsV2 = new_jobs_list
72
+
55
73
  output = await _get_dataset_check_owner(
56
74
  project_id=project_id,
57
75
  dataset_id=dataset_id,
@@ -92,7 +110,6 @@ async def apply_workflow(
92
110
  )
93
111
 
94
112
  # If backend is SLURM, check that the user has required attributes
95
- settings = Inject(get_settings)
96
113
  FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
97
114
  if FRACTAL_RUNNER_BACKEND == "slurm":
98
115
  if not user.slurm_user:
@@ -195,25 +212,24 @@ async def apply_workflow(
195
212
 
196
213
  # Define server-side job directory
197
214
  timestamp_string = get_timestamp().strftime("%Y%m%d_%H%M%S")
198
- WORKFLOW_DIR = (
199
- settings.FRACTAL_RUNNER_WORKING_BASE_DIR
200
- / (
201
- f"proj_v2_{project_id:07d}_wf_{workflow_id:07d}_job_{job.id:07d}"
202
- f"_{timestamp_string}"
203
- )
204
- ).resolve()
215
+ WORKFLOW_DIR_LOCAL = settings.FRACTAL_RUNNER_WORKING_BASE_DIR / (
216
+ f"proj_v2_{project_id:07d}_wf_{workflow_id:07d}_job_{job.id:07d}"
217
+ f"_{timestamp_string}"
218
+ )
205
219
 
206
220
  # Define user-side job directory
207
221
  if FRACTAL_RUNNER_BACKEND == "local":
208
- WORKFLOW_DIR_USER = WORKFLOW_DIR
222
+ WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
223
+ elif FRACTAL_RUNNER_BACKEND == "local_experimental":
224
+ WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
209
225
  elif FRACTAL_RUNNER_BACKEND == "slurm":
210
- WORKFLOW_DIR_USER = (
211
- Path(user.cache_dir) / f"{WORKFLOW_DIR.name}"
212
- ).resolve()
226
+ WORKFLOW_DIR_REMOTE = (
227
+ Path(user.cache_dir) / f"{WORKFLOW_DIR_LOCAL.name}"
228
+ )
213
229
 
214
230
  # Update job folders in the db
215
- job.working_dir = WORKFLOW_DIR.as_posix()
216
- job.working_dir_user = WORKFLOW_DIR_USER.as_posix()
231
+ job.working_dir = WORKFLOW_DIR_LOCAL.as_posix()
232
+ job.working_dir_user = WORKFLOW_DIR_REMOTE.as_posix()
217
233
  await db.merge(job)
218
234
  await db.commit()
219
235
 
@@ -226,6 +242,11 @@ async def apply_workflow(
226
242
  slurm_user=user.slurm_user,
227
243
  user_cache_dir=user.cache_dir,
228
244
  )
229
-
245
+ request.app.state.jobsV2.append(job.id)
246
+ logger.info(
247
+ f"Current worker's pid is {os.getpid()}. "
248
+ f"Current status of worker job's list "
249
+ f"{request.app.state.jobsV2}"
250
+ )
230
251
  await db.close()
231
252
  return job
@@ -1,13 +1,15 @@
1
1
  from io import BytesIO
2
2
  from pathlib import Path
3
+ from typing import Union
3
4
  from zipfile import ZIP_DEFLATED
4
5
  from zipfile import ZipFile
5
6
 
6
7
  from ...models.v1 import ApplyWorkflow
8
+ from ...models.v2 import JobV2
7
9
  from ...runner.filenames import SHUTDOWN_FILENAME
8
10
 
9
11
 
10
- def _write_shutdown_file(*, job: ApplyWorkflow):
12
+ def _write_shutdown_file(*, job: Union[ApplyWorkflow, JobV2]):
11
13
  """
12
14
  Write job's shutdown file.
13
15
 
@@ -5,15 +5,15 @@ from ....config import get_settings
5
5
  from ....syringe import Inject
6
6
 
7
7
 
8
- def _check_backend_is_slurm():
8
+ def _is_shutdown_available():
9
9
  """
10
10
  Raises:
11
11
  HTTPException(status_code=HTTP_422_UNPROCESSABLE_ENTITY):
12
- If FRACTAL_RUNNER_BACKEND is not 'slurm'
12
+ If FRACTAL_RUNNER_BACKEND is the thread-based 'local' backend.
13
13
  """
14
14
  settings = Inject(get_settings)
15
15
  backend = settings.FRACTAL_RUNNER_BACKEND
16
- if backend != "slurm":
16
+ if backend not in ["slurm", "local_experimental"]:
17
17
  raise HTTPException(
18
18
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
19
19
  detail=(