fractal-server 2.14.0a3__py3-none-any.whl → 2.14.0a5__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 (47) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +3 -1
  3. fractal_server/app/history/__init__.py +4 -4
  4. fractal_server/app/history/image_updates.py +124 -143
  5. fractal_server/app/history/status_enum.py +2 -2
  6. fractal_server/app/models/v2/__init__.py +6 -4
  7. fractal_server/app/models/v2/history.py +44 -20
  8. fractal_server/app/routes/api/__init__.py +1 -1
  9. fractal_server/app/routes/api/v2/__init__.py +4 -0
  10. fractal_server/app/routes/api/v2/_aux_functions_history.py +49 -0
  11. fractal_server/app/routes/api/v2/dataset.py +0 -12
  12. fractal_server/app/routes/api/v2/history.py +309 -186
  13. fractal_server/app/routes/api/v2/project.py +0 -25
  14. fractal_server/app/routes/api/v2/status_legacy.py +168 -0
  15. fractal_server/app/routes/api/v2/workflow.py +2 -17
  16. fractal_server/app/routes/api/v2/workflowtask.py +41 -71
  17. fractal_server/app/routes/auth/oauth.py +5 -3
  18. fractal_server/app/runner/executors/local/runner.py +10 -55
  19. fractal_server/app/runner/executors/slurm_sudo/runner.py +171 -108
  20. fractal_server/app/runner/v2/__init__.py +0 -20
  21. fractal_server/app/runner/v2/runner.py +45 -58
  22. fractal_server/app/runner/v2/runner_functions.py +164 -22
  23. fractal_server/app/schemas/_validators.py +13 -24
  24. fractal_server/app/schemas/user.py +10 -7
  25. fractal_server/app/schemas/user_settings.py +9 -21
  26. fractal_server/app/schemas/v2/dataset.py +8 -6
  27. fractal_server/app/schemas/v2/job.py +9 -5
  28. fractal_server/app/schemas/v2/manifest.py +2 -6
  29. fractal_server/app/schemas/v2/project.py +9 -7
  30. fractal_server/app/schemas/v2/task.py +41 -77
  31. fractal_server/app/schemas/v2/task_collection.py +14 -32
  32. fractal_server/app/schemas/v2/task_group.py +10 -9
  33. fractal_server/app/schemas/v2/workflow.py +10 -11
  34. fractal_server/app/security/signup_email.py +2 -2
  35. fractal_server/config.py +31 -32
  36. fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +120 -0
  37. fractal_server/tasks/v2/templates/2_pip_install.sh +1 -1
  38. fractal_server/tasks/v2/utils_templates.py +6 -0
  39. {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a5.dist-info}/METADATA +1 -1
  40. {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a5.dist-info}/RECORD +43 -44
  41. fractal_server/app/runner/executors/slurm_sudo/_executor_wait_thread.py +0 -130
  42. fractal_server/app/schemas/v2/history.py +0 -23
  43. fractal_server/migrations/versions/87cd72a537a2_add_historyitem_table.py +0 -68
  44. fractal_server/migrations/versions/954ddc64425a_image_status.py +0 -63
  45. {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a5.dist-info}/LICENSE +0 -0
  46. {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a5.dist-info}/WHEEL +0 -0
  47. {fractal_server-2.14.0a3.dist-info → fractal_server-2.14.0a5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,168 @@
1
+ from typing import Optional
2
+
3
+ from fastapi import APIRouter
4
+ from fastapi import Depends
5
+ from fastapi import HTTPException
6
+ from fastapi import status
7
+
8
+ from .....logger import set_logger
9
+ from ....db import AsyncSession
10
+ from ....db import get_async_db
11
+ from ....models.v2 import JobV2
12
+ from ....schemas.v2.status import StatusReadV2
13
+ from ....schemas.v2.workflowtask import WorkflowTaskStatusTypeV2
14
+ from ._aux_functions import _get_dataset_check_owner
15
+ from ._aux_functions import _get_submitted_jobs_statement
16
+ from ._aux_functions import _get_workflow_check_owner
17
+ from fractal_server.app.models import UserOAuth
18
+ from fractal_server.app.routes.auth import current_active_user
19
+
20
+ router = APIRouter()
21
+
22
+ logger = set_logger(__name__)
23
+
24
+
25
+ @router.get(
26
+ "/project/{project_id}/status-legacy/",
27
+ response_model=StatusReadV2,
28
+ )
29
+ async def get_workflowtask_status(
30
+ project_id: int,
31
+ dataset_id: int,
32
+ workflow_id: int,
33
+ user: UserOAuth = Depends(current_active_user),
34
+ db: AsyncSession = Depends(get_async_db),
35
+ ) -> Optional[StatusReadV2]:
36
+ """
37
+ Extract the status of all `WorkflowTaskV2` of a given `WorkflowV2` that ran
38
+ on a given `DatasetV2`.
39
+
40
+ *NOTE*: the current endpoint is not guaranteed to provide consistent
41
+ results if the workflow task list is modified in a non-trivial way
42
+ (that is, by adding intermediate tasks, removing tasks, or changing their
43
+ order). See fractal-server GitHub issues: 793, 1083.
44
+ """
45
+ # Get the dataset DB entry
46
+ output = await _get_dataset_check_owner(
47
+ project_id=project_id,
48
+ dataset_id=dataset_id,
49
+ user_id=user.id,
50
+ db=db,
51
+ )
52
+ dataset = output["dataset"]
53
+
54
+ # Get the workflow DB entry
55
+ workflow = await _get_workflow_check_owner(
56
+ project_id=project_id,
57
+ workflow_id=workflow_id,
58
+ user_id=user.id,
59
+ db=db,
60
+ )
61
+
62
+ # Check whether there exists a submitted job associated to this
63
+ # workflow/dataset pair. If it does exist, it will be used later.
64
+ # If there are multiple jobs, raise an error.
65
+ stm = _get_submitted_jobs_statement()
66
+ stm = stm.where(JobV2.dataset_id == dataset_id)
67
+ stm = stm.where(JobV2.workflow_id == workflow_id)
68
+ res = await db.execute(stm)
69
+ running_jobs = res.scalars().all()
70
+ if len(running_jobs) == 0:
71
+ running_job = None
72
+ elif len(running_jobs) == 1:
73
+ running_job = running_jobs[0]
74
+ else:
75
+ string_ids = str([job.id for job in running_jobs])[1:-1]
76
+ raise HTTPException(
77
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
78
+ detail=(
79
+ f"Cannot get WorkflowTaskV2 statuses as DatasetV2 {dataset.id}"
80
+ f" is linked to multiple active jobs: {string_ids}."
81
+ ),
82
+ )
83
+
84
+ # Initialize empty dictionary for WorkflowTaskV2 status
85
+ workflow_tasks_status_dict: dict = {}
86
+
87
+ # Lowest priority: read status from DB, which corresponds to jobs that are
88
+ # not running
89
+ history = dataset.history
90
+ for history_item in history:
91
+ wftask_id = history_item["workflowtask"]["id"]
92
+ wftask_status = history_item["status"]
93
+ workflow_tasks_status_dict[wftask_id] = wftask_status
94
+
95
+ if running_job is None:
96
+ # If no job is running, the chronological-last history item is also the
97
+ # positional-last workflow task to be included in the response.
98
+ if len(history) > 0:
99
+ last_valid_wftask_id = history[-1]["workflowtask"]["id"]
100
+ else:
101
+ last_valid_wftask_id = None
102
+ else:
103
+ # If a job is running, then gather more up-to-date information
104
+
105
+ # Mid priority: Set all WorkflowTask's that are part of the running job
106
+ # as "submitted"
107
+ start = running_job.first_task_index
108
+ end = running_job.last_task_index + 1
109
+
110
+ running_job_wftasks = workflow.task_list[start:end]
111
+ running_job_statuses = [
112
+ workflow_tasks_status_dict.get(wft.id, None)
113
+ for wft in running_job_wftasks
114
+ ]
115
+ try:
116
+ first_submitted_index = running_job_statuses.index(
117
+ WorkflowTaskStatusTypeV2.SUBMITTED
118
+ )
119
+ except ValueError:
120
+ logger.warning(
121
+ f"Job {running_job.id} is submitted but its task list does "
122
+ f"not contain a {WorkflowTaskStatusTypeV2.SUBMITTED} task."
123
+ )
124
+ first_submitted_index = 0
125
+
126
+ for wftask in running_job_wftasks[first_submitted_index:]:
127
+ workflow_tasks_status_dict[
128
+ wftask.id
129
+ ] = WorkflowTaskStatusTypeV2.SUBMITTED
130
+
131
+ # The last workflow task that is included in the submitted job is also
132
+ # the positional-last workflow task to be included in the response.
133
+ try:
134
+ last_valid_wftask_id = workflow.task_list[end - 1].id
135
+ except IndexError as e:
136
+ logger.warning(
137
+ f"Handled IndexError in `get_workflowtask_status` ({str(e)})."
138
+ )
139
+ logger.warning(
140
+ "Additional information: "
141
+ f"{running_job.first_task_index=}; "
142
+ f"{running_job.last_task_index=}; "
143
+ f"{len(workflow.task_list)=}; "
144
+ f"{dataset_id=}; "
145
+ f"{workflow_id=}."
146
+ )
147
+ last_valid_wftask_id = None
148
+ logger.warning(f"Now setting {last_valid_wftask_id=}.")
149
+
150
+ # Based on previously-gathered information, clean up the response body
151
+ clean_workflow_tasks_status_dict = {}
152
+ for wf_task in workflow.task_list:
153
+ wf_task_status = workflow_tasks_status_dict.get(wf_task.id)
154
+ if wf_task_status is None:
155
+ # If a wftask ID was not found, ignore it and continue
156
+ continue
157
+ clean_workflow_tasks_status_dict[str(wf_task.id)] = wf_task_status
158
+ if wf_task_status == WorkflowTaskStatusTypeV2.FAILED:
159
+ # Starting from the beginning of `workflow.task_list`, stop the
160
+ # first time that you hit a failed job
161
+ break
162
+ if wf_task.id == last_valid_wftask_id:
163
+ # Starting from the beginning of `workflow.task_list`, stop the
164
+ # first time that you hit `last_valid_wftask_id``
165
+ break
166
+
167
+ response_body = StatusReadV2(status=clean_workflow_tasks_status_dict)
168
+ return response_body
@@ -7,13 +7,10 @@ from fastapi import HTTPException
7
7
  from fastapi import Response
8
8
  from fastapi import status
9
9
  from pydantic import BaseModel
10
- from sqlmodel import delete
11
10
  from sqlmodel import select
12
11
 
13
12
  from ....db import AsyncSession
14
13
  from ....db import get_async_db
15
- from ....models.v2 import HistoryItemV2
16
- from ....models.v2 import ImageStatus
17
14
  from ....models.v2 import JobV2
18
15
  from ....models.v2 import ProjectV2
19
16
  from ....models.v2 import WorkflowV2
@@ -228,26 +225,14 @@ async def delete_workflow(
228
225
  ),
229
226
  )
230
227
 
231
- # Cascade operation: set foreign-keys to null for jobs and history items
232
- # which are in relationship with the current workflow.
228
+ # Cascade operation: set foreign-keys to null for jobs which are in
229
+ # relationship with the current workflow.
233
230
  stm = select(JobV2).where(JobV2.workflow_id == workflow_id)
234
231
  res = await db.execute(stm)
235
232
  jobs = res.scalars().all()
236
233
  for job in jobs:
237
234
  job.workflow_id = None
238
235
 
239
- wft_ids = [wft.id for wft in workflow.task_list]
240
- stm = select(HistoryItemV2).where(
241
- HistoryItemV2.workflowtask_id.in_(wft_ids)
242
- )
243
- res = await db.execute(stm)
244
- history_items = res.scalars().all()
245
- for history_item in history_items:
246
- history_item.workflowtask_id = None
247
-
248
- stm = delete(ImageStatus).where(ImageStatus.workflowtask_id.in_(wft_ids))
249
- await db.execute(stm)
250
-
251
236
  # Delete workflow
252
237
  await db.delete(workflow)
253
238
  await db.commit()
@@ -6,8 +6,6 @@ from fastapi import Depends
6
6
  from fastapi import HTTPException
7
7
  from fastapi import Response
8
8
  from fastapi import status
9
- from sqlmodel import delete
10
- from sqlmodel import select
11
9
 
12
10
  from ....db import AsyncSession
13
11
  from ....db import get_async_db
@@ -17,8 +15,6 @@ from ._aux_functions import _workflow_insert_task
17
15
  from ._aux_functions_tasks import _check_type_filters_compatibility
18
16
  from ._aux_functions_tasks import _get_task_read_access
19
17
  from fractal_server.app.models import UserOAuth
20
- from fractal_server.app.models.v2 import HistoryItemV2
21
- from fractal_server.app.models.v2 import ImageStatus
22
18
  from fractal_server.app.models.v2 import WorkflowTaskV2
23
19
  from fractal_server.app.routes.auth import current_active_user
24
20
  from fractal_server.app.schemas.v2 import WorkflowTaskCreateV2
@@ -39,80 +35,73 @@ async def replace_workflowtask(
39
35
  workflow_id: int,
40
36
  workflow_task_id: int,
41
37
  task_id: int,
42
- replace: Optional[WorkflowTaskReplaceV2] = None,
38
+ replace: WorkflowTaskReplaceV2,
43
39
  user: UserOAuth = Depends(current_active_user),
44
40
  db: AsyncSession = Depends(get_async_db),
45
41
  ) -> WorkflowTaskReadV2:
46
42
 
47
- old_workflow_task, workflow = await _get_workflow_task_check_owner(
43
+ # Get objects from database
44
+ old_wftask, workflow = await _get_workflow_task_check_owner(
48
45
  project_id=project_id,
49
46
  workflow_id=workflow_id,
50
47
  workflow_task_id=workflow_task_id,
51
48
  user_id=user.id,
52
49
  db=db,
53
50
  )
54
-
55
51
  new_task = await _get_task_read_access(
56
- task_id=task_id, user_id=user.id, db=db, require_active=True
52
+ task_id=task_id,
53
+ user_id=user.id,
54
+ db=db,
55
+ require_active=True,
57
56
  )
58
57
 
59
- if new_task.type != old_workflow_task.task.type:
58
+ # Preliminary checks
59
+ if old_wftask.task_type != new_task.type:
60
60
  raise HTTPException(
61
61
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
62
62
  detail=(
63
- f"Cannot replace a Task '{old_workflow_task.task.type}' with a"
64
- f" Task '{new_task.type}'."
63
+ "Cannot change task type from "
64
+ f"{old_wftask.task_type} to {new_task.type}."
65
65
  ),
66
66
  )
67
-
67
+ if replace.args_non_parallel is not None and new_task.type == "parallel":
68
+ raise HTTPException(
69
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
70
+ detail="Cannot set 'args_non_parallel' for parallel task.",
71
+ )
72
+ if replace.args_parallel is not None and new_task.type == "non_parallel":
73
+ raise HTTPException(
74
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
75
+ detail="Cannot set 'args_parallel' for non-parallel task.",
76
+ )
68
77
  _check_type_filters_compatibility(
69
78
  task_input_types=new_task.input_types,
70
- wftask_type_filters=old_workflow_task.type_filters,
79
+ wftask_type_filters=old_wftask.type_filters,
71
80
  )
72
81
 
73
- _args_non_parallel = old_workflow_task.args_non_parallel
74
- _args_parallel = old_workflow_task.args_parallel
75
- if replace is not None:
76
- if replace.args_non_parallel is not None:
77
- if new_task.type == "parallel":
78
- raise HTTPException(
79
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
80
- detail=(
81
- "Cannot set 'args_non_parallel' "
82
- "when Task is 'parallel'."
83
- ),
84
- )
85
- else:
86
- _args_non_parallel = replace.args_non_parallel
87
-
88
- if replace.args_parallel is not None:
89
- if new_task.type == "non_parallel":
90
- raise HTTPException(
91
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
92
- detail=(
93
- "Cannot set 'args_parallel' "
94
- "when Task is 'non_parallel'."
95
- ),
96
- )
97
- else:
98
- _args_parallel = replace.args_parallel
82
+ # Task arguments
83
+ if replace.args_non_parallel is None:
84
+ _args_non_parallel = old_wftask.args_non_parallel
85
+ else:
86
+ _args_non_parallel = replace.args_non_parallel
87
+ if replace.args_parallel is None:
88
+ _args_parallel = old_wftask.args_parallel
89
+ else:
90
+ _args_parallel = replace.args_parallel
99
91
 
100
92
  # If user's changes to `meta_non_parallel` are compatible with new task,
101
93
  # keep them; else, get `meta_non_parallel` from new task
102
94
  if (
103
- old_workflow_task.meta_non_parallel
104
- != old_workflow_task.task.meta_non_parallel
105
- ) and (
106
- old_workflow_task.task.meta_non_parallel == new_task.meta_non_parallel
107
- ):
108
- _meta_non_parallel = old_workflow_task.meta_non_parallel
95
+ old_wftask.meta_non_parallel != old_wftask.task.meta_non_parallel
96
+ ) and (old_wftask.task.meta_non_parallel == new_task.meta_non_parallel):
97
+ _meta_non_parallel = old_wftask.meta_non_parallel
109
98
  else:
110
99
  _meta_non_parallel = new_task.meta_non_parallel
111
100
  # Same for `meta_parallel`
112
- if (
113
- old_workflow_task.meta_parallel != old_workflow_task.task.meta_parallel
114
- ) and (old_workflow_task.task.meta_parallel == new_task.meta_parallel):
115
- _meta_parallel = old_workflow_task.meta_parallel
101
+ if (old_wftask.meta_parallel != old_wftask.task.meta_parallel) and (
102
+ old_wftask.task.meta_parallel == new_task.meta_parallel
103
+ ):
104
+ _meta_parallel = old_wftask.meta_parallel
116
105
  else:
117
106
  _meta_parallel = new_task.meta_parallel
118
107
 
@@ -121,7 +110,7 @@ async def replace_workflowtask(
121
110
  task_type=new_task.type,
122
111
  task=new_task,
123
112
  # old-task values
124
- type_filters=old_workflow_task.type_filters,
113
+ type_filters=old_wftask.type_filters,
125
114
  # possibly new values
126
115
  args_non_parallel=_args_non_parallel,
127
116
  args_parallel=_args_parallel,
@@ -129,8 +118,8 @@ async def replace_workflowtask(
129
118
  meta_parallel=_meta_parallel,
130
119
  )
131
120
 
132
- workflow_task_order = old_workflow_task.order
133
- workflow.task_list.remove(old_workflow_task)
121
+ workflow_task_order = old_wftask.order
122
+ workflow.task_list.remove(old_wftask)
134
123
  workflow.task_list.insert(workflow_task_order, new_workflow_task)
135
124
  await db.commit()
136
125
  await db.refresh(new_workflow_task)
@@ -205,8 +194,6 @@ async def create_workflowtask(
205
194
  db=db,
206
195
  )
207
196
 
208
- await db.close()
209
-
210
197
  return wftask_db
211
198
 
212
199
 
@@ -337,23 +324,6 @@ async def delete_workflowtask(
337
324
  db=db,
338
325
  )
339
326
 
340
- # Cascade operations:
341
- # * set foreign-keys to null for history items which are in relationship
342
- # with the current workflowtask;
343
- # * delete ImageStatus in relationship with the current workflowtask.
344
- stm = select(HistoryItemV2).where(
345
- HistoryItemV2.workflowtask_id == db_workflow_task.id
346
- )
347
- res = await db.execute(stm)
348
- history_items = res.scalars().all()
349
- for history_item in history_items:
350
- history_item.workflowtask_id = None
351
-
352
- stm = delete(ImageStatus).where(
353
- ImageStatus.workflowtask_id == db_workflow_task.id
354
- )
355
- await db.execute(stm)
356
-
357
327
  # Delete WorkflowTask
358
328
  await db.delete(db_workflow_task)
359
329
  await db.commit()
@@ -27,20 +27,22 @@ for client_config in settings.OAUTH_CLIENTS_CONFIG:
27
27
  from httpx_oauth.clients.google import GoogleOAuth2
28
28
 
29
29
  client = GoogleOAuth2(
30
- client_config.CLIENT_ID, client_config.CLIENT_SECRET
30
+ client_config.CLIENT_ID,
31
+ client_config.CLIENT_SECRET.get_secret_value(),
31
32
  )
32
33
  elif client_name == "github":
33
34
  from httpx_oauth.clients.github import GitHubOAuth2
34
35
 
35
36
  client = GitHubOAuth2(
36
- client_config.CLIENT_ID, client_config.CLIENT_SECRET
37
+ client_config.CLIENT_ID,
38
+ client_config.CLIENT_SECRET.get_secret_value(),
37
39
  )
38
40
  else:
39
41
  from httpx_oauth.clients.openid import OpenID
40
42
 
41
43
  client = OpenID(
42
44
  client_config.CLIENT_ID,
43
- client_config.CLIENT_SECRET,
45
+ client_config.CLIENT_SECRET.get_secret_value(),
44
46
  client_config.OIDC_CONFIGURATION_ENDPOINT,
45
47
  )
46
48
 
@@ -1,3 +1,4 @@
1
+ from concurrent.futures import Future
1
2
  from concurrent.futures import ThreadPoolExecutor
2
3
  from pathlib import Path
3
4
  from typing import Any
@@ -5,15 +6,12 @@ from typing import Optional
5
6
 
6
7
  from ._local_config import get_default_local_backend_config
7
8
  from ._local_config import LocalBackendConfig
8
- from fractal_server.app.history import HistoryItemImageStatus
9
- from fractal_server.app.history import update_all_images
10
- from fractal_server.app.history import update_single_image
11
- from fractal_server.app.history import update_single_image_logfile
12
9
  from fractal_server.app.runner.components import _COMPONENT_KEY_
13
10
  from fractal_server.app.runner.executors.base_runner import BaseRunner
14
11
  from fractal_server.app.runner.task_files import TaskFiles
15
12
  from fractal_server.logger import set_logger
16
13
 
14
+
17
15
  logger = set_logger(__name__)
18
16
 
19
17
 
@@ -51,10 +49,8 @@ class LocalRunner(BaseRunner):
51
49
  self,
52
50
  func: callable,
53
51
  parameters: dict[str, Any],
54
- history_item_id: int,
55
52
  task_files: TaskFiles,
56
- in_compound_task: bool = False,
57
- **kwargs,
53
+ local_backend_config: Optional[LocalBackendConfig] = None,
58
54
  ) -> tuple[Any, Exception]:
59
55
  logger.debug("[submit] START")
60
56
 
@@ -68,27 +64,17 @@ class LocalRunner(BaseRunner):
68
64
  self.validate_submit_parameters(parameters)
69
65
  workdir_local = current_task_files.wftask_subfolder_local
70
66
  workdir_local.mkdir()
67
+
71
68
  # SUBMISSION PHASE
72
69
  future = self.executor.submit(func, parameters=parameters)
73
70
 
74
71
  # RETRIEVAL PHASE
75
72
  try:
76
73
  result = future.result()
77
- if not in_compound_task:
78
- update_all_images(
79
- history_item_id=history_item_id,
80
- status=HistoryItemImageStatus.DONE,
81
- logfile=current_task_files.log_file_local,
82
- )
83
74
  logger.debug(f"[submit] END {result=}")
84
75
  return result, None
85
76
  except Exception as e:
86
77
  exception = e
87
- update_all_images(
88
- history_item_id=history_item_id,
89
- status=HistoryItemImageStatus.FAILED,
90
- logfile=current_task_files.log_file_local,
91
- )
92
78
  logger.debug(f"[submit] END {exception=}")
93
79
  return None, exception
94
80
 
@@ -96,11 +82,9 @@ class LocalRunner(BaseRunner):
96
82
  self,
97
83
  func: callable,
98
84
  list_parameters: list[dict],
99
- history_item_id: int,
100
85
  task_files: TaskFiles,
101
86
  in_compound_task: bool = False,
102
87
  local_backend_config: Optional[LocalBackendConfig] = None,
103
- **kwargs,
104
88
  ):
105
89
  logger.debug(f"[multisubmit] START, {len(list_parameters)=}")
106
90
 
@@ -126,13 +110,12 @@ class LocalRunner(BaseRunner):
126
110
  original_task_files = task_files
127
111
 
128
112
  # Execute tasks, in chunks of size `parallel_tasks_per_job`
129
- results = {}
130
- exceptions = {}
113
+ results: dict[int, Any] = {}
114
+ exceptions: dict[int, BaseException] = {}
131
115
  for ind_chunk in range(0, n_elements, parallel_tasks_per_job):
132
116
  list_parameters_chunk = list_parameters[
133
117
  ind_chunk : ind_chunk + parallel_tasks_per_job
134
118
  ]
135
- from concurrent.futures import Future
136
119
 
137
120
  active_futures: dict[int, Future] = {}
138
121
  active_task_files: dict[int, TaskFiles] = {}
@@ -156,45 +139,17 @@ class LocalRunner(BaseRunner):
156
139
  ]
157
140
  for positional_index, fut in finished_futures:
158
141
  active_futures.pop(positional_index)
159
- current_task_files = active_task_files.pop(
160
- positional_index
161
- )
142
+ # current_task_files = active_task_files.pop(
143
+ # positional_index
144
+ # )
162
145
  zarr_url = list_parameters[positional_index]["zarr_url"]
163
- if not in_compound_task:
164
- update_single_image_logfile(
165
- history_item_id=history_item_id,
166
- zarr_url=zarr_url,
167
- logfile=current_task_files.log_file_local,
168
- )
169
146
  try:
170
147
  results[positional_index] = fut.result()
171
148
  print(f"Mark {zarr_url=} as done, {kwargs}")
172
- if not in_compound_task:
173
- update_single_image(
174
- history_item_id=history_item_id,
175
- zarr_url=zarr_url,
176
- status=HistoryItemImageStatus.DONE,
177
- )
178
149
  except Exception as e:
179
150
  print(f"Mark {zarr_url=} as failed, {kwargs} - {e}")
180
151
  exceptions[positional_index] = e
181
- if not in_compound_task:
182
- update_single_image(
183
- history_item_id=history_item_id,
184
- zarr_url=zarr_url,
185
- status=HistoryItemImageStatus.FAILED,
186
- )
187
- if in_compound_task:
188
- if exceptions == {}:
189
- update_all_images(
190
- history_item_id=history_item_id,
191
- status=HistoryItemImageStatus.DONE,
192
- )
193
- else:
194
- update_all_images(
195
- history_item_id=history_item_id,
196
- status=HistoryItemImageStatus.FAILED,
197
- )
152
+
198
153
  logger.debug(f"[multisubmit] END, {results=}, {exceptions=}")
199
154
 
200
155
  return results, exceptions