fractal-server 2.9.0a2__py3-none-any.whl → 2.9.0a3__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 (30) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/routes/api/v2/task_collection.py +3 -10
  3. fractal_server/app/routes/api/v2/task_group_lifecycle.py +50 -6
  4. fractal_server/app/schemas/_validators.py +0 -14
  5. fractal_server/app/schemas/v1/applyworkflow.py +0 -8
  6. fractal_server/app/schemas/v1/dataset.py +0 -5
  7. fractal_server/app/schemas/v1/project.py +0 -5
  8. fractal_server/app/schemas/v1/state.py +0 -5
  9. fractal_server/app/schemas/v1/workflow.py +0 -5
  10. fractal_server/app/schemas/v2/dataset.py +0 -6
  11. fractal_server/app/schemas/v2/job.py +0 -8
  12. fractal_server/app/schemas/v2/project.py +0 -5
  13. fractal_server/app/schemas/v2/workflow.py +0 -5
  14. fractal_server/ssh/_fabric.py +4 -2
  15. fractal_server/tasks/v2/local/{utils_local.py → _utils.py} +25 -0
  16. fractal_server/tasks/v2/local/collect.py +2 -2
  17. fractal_server/tasks/v2/local/deactivate.py +1 -1
  18. fractal_server/tasks/v2/local/reactivate.py +1 -1
  19. fractal_server/tasks/v2/ssh/__init__.py +3 -0
  20. fractal_server/tasks/v2/ssh/_utils.py +87 -0
  21. fractal_server/tasks/v2/ssh/collect.py +3 -78
  22. fractal_server/tasks/v2/ssh/deactivate.py +243 -2
  23. fractal_server/tasks/v2/ssh/reactivate.py +199 -2
  24. fractal_server/tasks/v2/utils_background.py +0 -24
  25. fractal_server/zip_tools.py +21 -4
  26. {fractal_server-2.9.0a2.dist-info → fractal_server-2.9.0a3.dist-info}/METADATA +1 -1
  27. {fractal_server-2.9.0a2.dist-info → fractal_server-2.9.0a3.dist-info}/RECORD +30 -29
  28. {fractal_server-2.9.0a2.dist-info → fractal_server-2.9.0a3.dist-info}/LICENSE +0 -0
  29. {fractal_server-2.9.0a2.dist-info → fractal_server-2.9.0a3.dist-info}/WHEEL +0 -0
  30. {fractal_server-2.9.0a2.dist-info → fractal_server-2.9.0a3.dist-info}/entry_points.txt +0 -0
@@ -1 +1 @@
1
- __VERSION__ = "2.9.0a2"
1
+ __VERSION__ = "2.9.0a3"
@@ -37,6 +37,7 @@ from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
37
37
  from fractal_server.tasks.v2.local.collect import (
38
38
  collect_local,
39
39
  )
40
+ from fractal_server.tasks.v2.ssh import collect_ssh
40
41
  from fractal_server.tasks.v2.utils_package_names import _parse_wheel_filename
41
42
  from fractal_server.tasks.v2.utils_package_names import normalize_package_name
42
43
  from fractal_server.tasks.v2.utils_python_interpreter import (
@@ -63,10 +64,7 @@ async def collect_tasks_pip(
63
64
  db: AsyncSession = Depends(get_async_db),
64
65
  ) -> TaskGroupActivityV2Read:
65
66
  """
66
- Task collection endpoint
67
-
68
- Trigger the creation of a dedicated virtual environment, the installation
69
- of a package and the collection of tasks as advertised in the manifest.
67
+ Task-collection endpoint
70
68
  """
71
69
 
72
70
  # Get settings
@@ -246,12 +244,7 @@ async def collect_tasks_pip(
246
244
 
247
245
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
248
246
  # SSH task collection
249
-
250
- from fractal_server.tasks.v2.ssh.collect import (
251
- collect_ssh,
252
- )
253
-
254
- # User appropriate FractalSSH object
247
+ # Use appropriate FractalSSH object
255
248
  ssh_credentials = dict(
256
249
  user=user_settings.ssh_username,
257
250
  host=user_settings.ssh_host,
@@ -2,9 +2,11 @@ from fastapi import APIRouter
2
2
  from fastapi import BackgroundTasks
3
3
  from fastapi import Depends
4
4
  from fastapi import HTTPException
5
+ from fastapi import Request
5
6
  from fastapi import Response
6
7
  from fastapi import status
7
8
 
9
+ from ...aux.validate_user_settings import validate_user_settings
8
10
  from ._aux_functions_task_lifecycle import check_no_ongoing_activity
9
11
  from ._aux_functions_tasks import _get_task_group_full_access
10
12
  from fractal_server.app.db import AsyncSession
@@ -22,6 +24,8 @@ from fractal_server.logger import set_logger
22
24
  from fractal_server.syringe import Inject
23
25
  from fractal_server.tasks.v2.local import deactivate_local
24
26
  from fractal_server.tasks.v2.local import reactivate_local
27
+ from fractal_server.tasks.v2.ssh import deactivate_ssh
28
+ from fractal_server.tasks.v2.ssh import reactivate_ssh
25
29
  from fractal_server.utils import get_timestamp
26
30
 
27
31
  router = APIRouter()
@@ -38,6 +42,7 @@ async def deactivate_task_group(
38
42
  task_group_id: int,
39
43
  background_tasks: BackgroundTasks,
40
44
  response: Response,
45
+ request: Request,
41
46
  user: UserOAuth = Depends(current_active_user),
42
47
  db: AsyncSession = Depends(get_async_db),
43
48
  ) -> TaskGroupReadV2:
@@ -103,10 +108,29 @@ async def deactivate_task_group(
103
108
  # Submit background task
104
109
  settings = Inject(get_settings)
105
110
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
106
- raise HTTPException(
107
- status_code=status.HTTP_501_NOT_IMPLEMENTED,
108
- detail="Not implemented (yet) for SSH.",
111
+
112
+ # Validate user settings (backend-specific)
113
+ user_settings = await validate_user_settings(
114
+ user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
115
+ )
116
+
117
+ # User appropriate FractalSSH object
118
+ ssh_credentials = dict(
119
+ user=user_settings.ssh_username,
120
+ host=user_settings.ssh_host,
121
+ key_path=user_settings.ssh_private_key_path,
122
+ )
123
+ fractal_ssh_list = request.app.state.fractal_ssh_list
124
+ fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
125
+
126
+ background_tasks.add_task(
127
+ deactivate_ssh,
128
+ task_group_id=task_group.id,
129
+ task_group_activity_id=task_group_activity.id,
130
+ fractal_ssh=fractal_ssh,
131
+ tasks_base_dir=user_settings.ssh_tasks_dir,
109
132
  )
133
+
110
134
  else:
111
135
  background_tasks.add_task(
112
136
  deactivate_local,
@@ -130,6 +154,7 @@ async def reactivate_task_group(
130
154
  task_group_id: int,
131
155
  background_tasks: BackgroundTasks,
132
156
  response: Response,
157
+ request: Request,
133
158
  user: UserOAuth = Depends(current_active_user),
134
159
  db: AsyncSession = Depends(get_async_db),
135
160
  ) -> TaskGroupReadV2:
@@ -203,10 +228,29 @@ async def reactivate_task_group(
203
228
  # Submit background task
204
229
  settings = Inject(get_settings)
205
230
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
206
- raise HTTPException(
207
- status_code=status.HTTP_501_NOT_IMPLEMENTED,
208
- detail="Not implemented (yet) for SSH.",
231
+
232
+ # Validate user settings (backend-specific)
233
+ user_settings = await validate_user_settings(
234
+ user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
235
+ )
236
+
237
+ # Use appropriate FractalSSH object
238
+ ssh_credentials = dict(
239
+ user=user_settings.ssh_username,
240
+ host=user_settings.ssh_host,
241
+ key_path=user_settings.ssh_private_key_path,
242
+ )
243
+ fractal_ssh_list = request.app.state.fractal_ssh_list
244
+ fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
245
+
246
+ background_tasks.add_task(
247
+ reactivate_ssh,
248
+ task_group_id=task_group.id,
249
+ task_group_activity_id=task_group_activity.id,
250
+ fractal_ssh=fractal_ssh,
251
+ tasks_base_dir=user_settings.ssh_tasks_dir,
209
252
  )
253
+
210
254
  else:
211
255
  background_tasks.add_task(
212
256
  reactivate_local,
@@ -1,6 +1,4 @@
1
1
  import os
2
- from datetime import datetime
3
- from datetime import timezone
4
2
  from typing import Any
5
3
  from typing import Optional
6
4
 
@@ -103,15 +101,3 @@ def val_unique_list(attribute: str):
103
101
  return must_be_unique
104
102
 
105
103
  return val
106
-
107
-
108
- def valutc(attribute: str):
109
- def val(timestamp: Optional[datetime]) -> Optional[datetime]:
110
- """
111
- Replace `tzinfo` with `timezone.utc`.
112
- """
113
- if timestamp is not None:
114
- return timestamp.replace(tzinfo=timezone.utc)
115
- return None
116
-
117
- return val
@@ -7,7 +7,6 @@ from pydantic import validator
7
7
  from pydantic.types import StrictStr
8
8
 
9
9
  from .._validators import valstr
10
- from .._validators import valutc
11
10
  from .dumps import DatasetDumpV1
12
11
  from .dumps import ProjectDumpV1
13
12
  from .dumps import WorkflowDumpV1
@@ -150,13 +149,6 @@ class ApplyWorkflowReadV1(_ApplyWorkflowBaseV1):
150
149
  first_task_index: Optional[int]
151
150
  last_task_index: Optional[int]
152
151
 
153
- _start_timestamp = validator("start_timestamp", allow_reuse=True)(
154
- valutc("start_timestamp")
155
- )
156
- _end_timestamp = validator("end_timestamp", allow_reuse=True)(
157
- valutc("end_timestamp")
158
- )
159
-
160
152
 
161
153
  class ApplyWorkflowUpdateV1(BaseModel):
162
154
  """
@@ -8,7 +8,6 @@ from pydantic import validator
8
8
 
9
9
  from .._validators import val_absolute_path
10
10
  from .._validators import valstr
11
- from .._validators import valutc
12
11
  from .dumps import WorkflowTaskDumpV1
13
12
  from .project import ProjectReadV1
14
13
  from .workflow import WorkflowTaskStatusTypeV1
@@ -151,10 +150,6 @@ class DatasetReadV1(_DatasetBaseV1):
151
150
  project: ProjectReadV1
152
151
  timestamp_created: datetime
153
152
 
154
- _timestamp_created = validator("timestamp_created", allow_reuse=True)(
155
- valutc("timestamp_created")
156
- )
157
-
158
153
 
159
154
  class DatasetStatusReadV1(BaseModel):
160
155
  """
@@ -5,7 +5,6 @@ from pydantic import BaseModel
5
5
  from pydantic import validator
6
6
 
7
7
  from .._validators import valstr
8
- from .._validators import valutc
9
8
 
10
9
 
11
10
  __all__ = (
@@ -50,10 +49,6 @@ class ProjectReadV1(_ProjectBaseV1):
50
49
  id: int
51
50
  timestamp_created: datetime
52
51
 
53
- _timestamp_created = validator("timestamp_created", allow_reuse=True)(
54
- valutc("timestamp_created")
55
- )
56
-
57
52
 
58
53
  class ProjectUpdateV1(_ProjectBaseV1):
59
54
  """
@@ -3,9 +3,6 @@ from typing import Any
3
3
  from typing import Optional
4
4
 
5
5
  from pydantic import BaseModel
6
- from pydantic import validator
7
-
8
- from fractal_server.app.schemas._validators import valutc
9
6
 
10
7
 
11
8
  class StateRead(BaseModel):
@@ -19,5 +16,3 @@ class StateRead(BaseModel):
19
16
  id: Optional[int]
20
17
  data: dict[str, Any]
21
18
  timestamp: datetime
22
-
23
- _timestamp = validator("timestamp", allow_reuse=True)(valutc("timestamp"))
@@ -8,7 +8,6 @@ from pydantic import validator
8
8
 
9
9
  from .._validators import valint
10
10
  from .._validators import valstr
11
- from .._validators import valutc
12
11
  from .project import ProjectReadV1
13
12
  from .task import TaskExportV1
14
13
  from .task import TaskImportV1
@@ -135,10 +134,6 @@ class WorkflowReadV1(_WorkflowBaseV1):
135
134
  project: ProjectReadV1
136
135
  timestamp_created: datetime
137
136
 
138
- _timestamp_created = validator("timestamp_created", allow_reuse=True)(
139
- valutc("timestamp_created")
140
- )
141
-
142
137
 
143
138
  class WorkflowCreateV1(_WorkflowBaseV1):
144
139
  """
@@ -7,7 +7,6 @@ from pydantic import Field
7
7
  from pydantic import validator
8
8
 
9
9
  from .._validators import valstr
10
- from .._validators import valutc
11
10
  from .dumps import WorkflowTaskDumpV2
12
11
  from .project import ProjectReadV2
13
12
  from .workflowtask import WorkflowTaskStatusTypeV2
@@ -62,11 +61,6 @@ class DatasetReadV2(BaseModel):
62
61
  zarr_dir: str
63
62
  filters: Filters = Field(default_factory=Filters)
64
63
 
65
- # Validators
66
- _timestamp_created = validator("timestamp_created", allow_reuse=True)(
67
- valutc("timestamp_created")
68
- )
69
-
70
64
 
71
65
  class DatasetUpdateV2(BaseModel, extra=Extra.forbid):
72
66
 
@@ -8,7 +8,6 @@ from pydantic import validator
8
8
  from pydantic.types import StrictStr
9
9
 
10
10
  from .._validators import valstr
11
- from .._validators import valutc
12
11
  from .dumps import DatasetDumpV2
13
12
  from .dumps import ProjectDumpV2
14
13
  from .dumps import WorkflowDumpV2
@@ -101,13 +100,6 @@ class JobReadV2(BaseModel):
101
100
  last_task_index: Optional[int]
102
101
  worker_init: Optional[str]
103
102
 
104
- _start_timestamp = validator("start_timestamp", allow_reuse=True)(
105
- valutc("start_timestamp")
106
- )
107
- _end_timestamp = validator("end_timestamp", allow_reuse=True)(
108
- valutc("end_timestamp")
109
- )
110
-
111
103
 
112
104
  class JobUpdateV2(BaseModel, extra=Extra.forbid):
113
105
 
@@ -6,7 +6,6 @@ from pydantic import Extra
6
6
  from pydantic import validator
7
7
 
8
8
  from .._validators import valstr
9
- from .._validators import valutc
10
9
 
11
10
 
12
11
  class ProjectCreateV2(BaseModel, extra=Extra.forbid):
@@ -21,10 +20,6 @@ class ProjectReadV2(BaseModel):
21
20
  id: int
22
21
  name: str
23
22
  timestamp_created: datetime
24
- # Validators
25
- _timestamp_created = validator("timestamp_created", allow_reuse=True)(
26
- valutc("timestamp_created")
27
- )
28
23
 
29
24
 
30
25
  class ProjectUpdateV2(BaseModel, extra=Extra.forbid):
@@ -6,7 +6,6 @@ from pydantic import Extra
6
6
  from pydantic import validator
7
7
 
8
8
  from .._validators import valstr
9
- from .._validators import valutc
10
9
  from .project import ProjectReadV2
11
10
  from .workflowtask import WorkflowTaskExportV2
12
11
  from .workflowtask import WorkflowTaskImportV2
@@ -31,10 +30,6 @@ class WorkflowReadV2(BaseModel):
31
30
  project: ProjectReadV2
32
31
  timestamp_created: datetime
33
32
 
34
- _timestamp_created = validator("timestamp_created", allow_reuse=True)(
35
- valutc("timestamp_created")
36
- )
37
-
38
33
 
39
34
  class WorkflowReadV2WithWarnings(WorkflowReadV2):
40
35
  task_list: list[WorkflowTaskReadV2WithWarning]
@@ -321,8 +321,10 @@ class FractalSSH(object):
321
321
  f"{prefix} END running '{cmd}' over SSH, "
322
322
  f"elapsed {t_1-t_0:.3f}"
323
323
  )
324
- self.logger.debug(f"STDOUT: {res.stdout}")
325
- self.logger.debug(f"STDERR: {res.stderr}")
324
+ self.logger.debug("STDOUT:")
325
+ self.logger.debug(res.stdout)
326
+ self.logger.debug("STDERR:")
327
+ self.logger.debug(res.stderr)
326
328
  return res.stdout
327
329
  except NoValidConnectionsError as e:
328
330
  # Case 2: Command fails with a connection error
@@ -1,5 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
+ from fractal_server.app.schemas.v2 import TaskCreateV2
3
4
  from fractal_server.logger import get_logger
4
5
  from fractal_server.tasks.v2.utils_templates import customize_template
5
6
  from fractal_server.utils import execute_command_sync
@@ -43,3 +44,27 @@ def _customize_and_run_template(
43
44
  stdout = execute_command_sync(command=cmd, logger_name=logger_name)
44
45
  logger.debug(f"_customize_and_run_template {template_filename} - END")
45
46
  return stdout
47
+
48
+
49
+ def check_task_files_exist(task_list: list[TaskCreateV2]) -> None:
50
+ """
51
+ Check that the modules listed in task commands point to existing files.
52
+
53
+ Args:
54
+ task_list:
55
+ """
56
+ for _task in task_list:
57
+ if _task.command_non_parallel is not None:
58
+ _task_path = _task.command_non_parallel.split()[1]
59
+ if not Path(_task_path).exists():
60
+ raise FileNotFoundError(
61
+ f"Task `{_task.name}` has `command_non_parallel` "
62
+ f"pointing to missing file `{_task_path}`."
63
+ )
64
+ if _task.command_parallel is not None:
65
+ _task_path = _task.command_parallel.split()[1]
66
+ if not Path(_task_path).exists():
67
+ raise FileNotFoundError(
68
+ f"Task `{_task.name}` has `command_parallel` "
69
+ f"pointing to missing file `{_task_path}`."
70
+ )
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from tempfile import TemporaryDirectory
7
7
 
8
8
  from ..utils_database import create_db_tasks_and_update_task_group
9
- from .utils_local import _customize_and_run_template
9
+ from ._utils import _customize_and_run_template
10
10
  from fractal_server.app.db import get_sync_db
11
11
  from fractal_server.app.models.v2 import TaskGroupActivityV2
12
12
  from fractal_server.app.models.v2 import TaskGroupV2
@@ -16,9 +16,9 @@ from fractal_server.app.schemas.v2.manifest import ManifestV2
16
16
  from fractal_server.logger import get_logger
17
17
  from fractal_server.logger import set_logger
18
18
  from fractal_server.tasks.utils import get_log_path
19
+ from fractal_server.tasks.v2.local._utils import check_task_files_exist
19
20
  from fractal_server.tasks.v2.utils_background import _prepare_tasks_metadata
20
21
  from fractal_server.tasks.v2.utils_background import add_commit_refresh
21
- from fractal_server.tasks.v2.utils_background import check_task_files_exist
22
22
  from fractal_server.tasks.v2.utils_background import fail_and_cleanup
23
23
  from fractal_server.tasks.v2.utils_background import get_current_log
24
24
  from fractal_server.tasks.v2.utils_package_names import compare_package_names
@@ -7,7 +7,7 @@ from tempfile import TemporaryDirectory
7
7
  from ..utils_background import add_commit_refresh
8
8
  from ..utils_background import fail_and_cleanup
9
9
  from ..utils_templates import get_collection_replacements
10
- from .utils_local import _customize_and_run_template
10
+ from ._utils import _customize_and_run_template
11
11
  from fractal_server.app.db import get_sync_db
12
12
  from fractal_server.app.models.v2 import TaskGroupActivityV2
13
13
  from fractal_server.app.models.v2 import TaskGroupV2
@@ -7,7 +7,7 @@ from tempfile import TemporaryDirectory
7
7
  from ..utils_background import add_commit_refresh
8
8
  from ..utils_background import fail_and_cleanup
9
9
  from ..utils_templates import get_collection_replacements
10
- from .utils_local import _customize_and_run_template
10
+ from ._utils import _customize_and_run_template
11
11
  from fractal_server.app.db import get_sync_db
12
12
  from fractal_server.app.models.v2 import TaskGroupActivityV2
13
13
  from fractal_server.app.models.v2 import TaskGroupV2
@@ -0,0 +1,3 @@
1
+ from .collect import collect_ssh # noqa
2
+ from .deactivate import deactivate_ssh # noqa
3
+ from .reactivate import reactivate_ssh # noqa
@@ -0,0 +1,87 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from fractal_server.app.models.v2 import TaskGroupV2
5
+ from fractal_server.logger import get_logger
6
+ from fractal_server.ssh._fabric import FractalSSH
7
+ from fractal_server.tasks.v2.utils_templates import customize_template
8
+
9
+
10
+ def _customize_and_run_template(
11
+ *,
12
+ template_filename: str,
13
+ replacements: list[tuple[str, str]],
14
+ script_dir_local: str,
15
+ prefix: str,
16
+ fractal_ssh: FractalSSH,
17
+ script_dir_remote: str,
18
+ logger_name: str,
19
+ ) -> str:
20
+ """
21
+ Customize one of the template bash scripts, transfer it to the remote host
22
+ via SFTP and then run it via SSH.
23
+
24
+ Args:
25
+ template_filename: Filename of the template file (ends with ".sh").
26
+ replacements: Dictionary of replacements.
27
+ script_dir: Local folder where the script will be placed.
28
+ prefix: Prefix for the script filename.
29
+ fractal_ssh: FractalSSH object
30
+ script_dir_remote: Remote scripts directory
31
+ """
32
+ logger = get_logger(logger_name=logger_name)
33
+ logger.debug(f"_customize_and_run_template {template_filename} - START")
34
+ # Prepare name and path of script
35
+ if not template_filename.endswith(".sh"):
36
+ raise ValueError(
37
+ f"Invalid {template_filename=} (it must end with '.sh')."
38
+ )
39
+ script_filename = f"{prefix}_{template_filename}"
40
+ script_path_local = (Path(script_dir_local) / script_filename).as_posix()
41
+
42
+ customize_template(
43
+ template_name=template_filename,
44
+ replacements=replacements,
45
+ script_path=script_path_local,
46
+ )
47
+
48
+ # Transfer script to remote host
49
+ script_path_remote = os.path.join(
50
+ script_dir_remote,
51
+ script_filename,
52
+ )
53
+ logger.debug(f"Now transfer {script_path_local=} over SSH.")
54
+ fractal_ssh.send_file(
55
+ local=script_path_local,
56
+ remote=script_path_remote,
57
+ )
58
+
59
+ # Execute script remotely
60
+ cmd = f"bash {script_path_remote}"
61
+ logger.debug(f"Now run '{cmd}' over SSH.")
62
+ stdout = fractal_ssh.run_command(cmd=cmd)
63
+
64
+ logger.debug(f"_customize_and_run_template {template_filename} - END")
65
+ return stdout
66
+
67
+
68
+ def _copy_wheel_file_ssh(
69
+ *, task_group: TaskGroupV2, fractal_ssh: FractalSSH, logger_name: str
70
+ ) -> str:
71
+ """
72
+ Handle the situation where `task_group.wheel_path` is not part of
73
+ `task_group.path`, by copying `wheel_path` into `path`.
74
+
75
+ Returns:
76
+ The new `wheel_path`.
77
+ """
78
+ logger = get_logger(logger_name=logger_name)
79
+ source = task_group.wheel_path
80
+ dest = (
81
+ Path(task_group.path) / Path(task_group.wheel_path).name
82
+ ).as_posix()
83
+ cmd = f"cp {source} {dest}"
84
+ logger.debug(f"[_copy_wheel_file] START {source=} {dest=}")
85
+ fractal_ssh.run_command(cmd=cmd)
86
+ logger.debug(f"[_copy_wheel_file] END {source=} {dest=}")
87
+ return dest
@@ -1,5 +1,4 @@
1
1
  import logging
2
- import os
3
2
  import time
4
3
  from pathlib import Path
5
4
  from tempfile import TemporaryDirectory
@@ -13,16 +12,16 @@ from fractal_server.app.models.v2 import TaskGroupV2
13
12
  from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
14
13
  from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
15
14
  from fractal_server.app.schemas.v2.manifest import ManifestV2
16
- from fractal_server.logger import get_logger
17
15
  from fractal_server.logger import set_logger
18
16
  from fractal_server.ssh._fabric import FractalSSH
17
+ from fractal_server.tasks.v2.ssh._utils import _copy_wheel_file_ssh
18
+ from fractal_server.tasks.v2.ssh._utils import _customize_and_run_template
19
19
  from fractal_server.tasks.v2.utils_background import add_commit_refresh
20
20
  from fractal_server.tasks.v2.utils_background import get_current_log
21
21
  from fractal_server.tasks.v2.utils_package_names import compare_package_names
22
22
  from fractal_server.tasks.v2.utils_python_interpreter import (
23
23
  get_python_interpreter_v2,
24
24
  )
25
- from fractal_server.tasks.v2.utils_templates import customize_template
26
25
  from fractal_server.tasks.v2.utils_templates import get_collection_replacements
27
26
  from fractal_server.tasks.v2.utils_templates import (
28
27
  parse_script_pip_show_stdout,
@@ -33,81 +32,6 @@ from fractal_server.utils import get_timestamp
33
32
  LOGGER_NAME = __name__
34
33
 
35
34
 
36
- def _customize_and_run_template(
37
- *,
38
- template_filename: str,
39
- replacements: list[tuple[str, str]],
40
- script_dir_local: str,
41
- prefix: str,
42
- fractal_ssh: FractalSSH,
43
- script_dir_remote: str,
44
- logger_name: str,
45
- ) -> str:
46
- """
47
- Customize one of the template bash scripts, transfer it to the remote host
48
- via SFTP and then run it via SSH.
49
-
50
- Args:
51
- template_filename: Filename of the template file (ends with ".sh").
52
- replacements: Dictionary of replacements.
53
- script_dir: Local folder where the script will be placed.
54
- prefix: Prefix for the script filename.
55
- fractal_ssh: FractalSSH object
56
- script_dir_remote: Remote scripts directory
57
- """
58
- logger = get_logger(logger_name=logger_name)
59
- logger.debug(f"_customize_and_run_template {template_filename} - START")
60
-
61
- # Prepare name and path of script
62
- if not template_filename.endswith(".sh"):
63
- raise ValueError(
64
- f"Invalid {template_filename=} (it must end with '.sh')."
65
- )
66
- script_filename = f"{prefix}_{template_filename}"
67
- script_path_local = Path(script_dir_local) / script_filename
68
-
69
- customize_template(
70
- template_name=template_filename,
71
- replacements=replacements,
72
- script_path=script_path_local,
73
- )
74
-
75
- # Transfer script to remote host
76
- script_path_remote = os.path.join(
77
- script_dir_remote,
78
- script_filename,
79
- )
80
- logger.debug(f"Now transfer {script_path_local=} over SSH.")
81
- fractal_ssh.send_file(
82
- local=script_path_local,
83
- remote=script_path_remote,
84
- )
85
-
86
- # Execute script remotely
87
- cmd = f"bash {script_path_remote}"
88
- logger.debug(f"Now run '{cmd}' over SSH.")
89
- stdout = fractal_ssh.run_command(cmd=cmd)
90
- logger.debug(f"Standard output of '{cmd}':\n{stdout}")
91
-
92
- logger.debug(f"_customize_and_run_template {template_filename} - END")
93
- return stdout
94
-
95
-
96
- def _copy_wheel_file_ssh(
97
- task_group: TaskGroupV2, fractal_ssh: FractalSSH
98
- ) -> str:
99
- logger = get_logger(LOGGER_NAME)
100
- source = task_group.wheel_path
101
- dest = (
102
- Path(task_group.path) / Path(task_group.wheel_path).name
103
- ).as_posix()
104
- cmd = f"cp {source} {dest}"
105
- logger.debug(f"[_copy_wheel_file] START {source=} {dest=}")
106
- fractal_ssh.run_command(cmd=cmd)
107
- logger.debug(f"[_copy_wheel_file] END {source=} {dest=}")
108
- return dest
109
-
110
-
111
35
  def collect_ssh(
112
36
  *,
113
37
  task_group_id: int,
@@ -232,6 +156,7 @@ def collect_ssh(
232
156
  new_wheel_path = _copy_wheel_file_ssh(
233
157
  task_group=task_group,
234
158
  fractal_ssh=fractal_ssh,
159
+ logger_name=LOGGER_NAME,
235
160
  )
236
161
  task_group.wheel_path = new_wheel_path
237
162
  task_group = add_commit_refresh(obj=task_group, db=db)
@@ -1,2 +1,243 @@
1
- def deactivate_ssh():
2
- pass
1
+ import logging
2
+ import time
3
+ from pathlib import Path
4
+ from tempfile import TemporaryDirectory
5
+
6
+ from ..utils_background import add_commit_refresh
7
+ from ..utils_background import fail_and_cleanup
8
+ from ..utils_templates import get_collection_replacements
9
+ from ._utils import _copy_wheel_file_ssh
10
+ from ._utils import _customize_and_run_template
11
+ from fractal_server.app.db import get_sync_db
12
+ from fractal_server.app.models.v2 import TaskGroupActivityV2
13
+ from fractal_server.app.models.v2 import TaskGroupV2
14
+ from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
15
+ from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
16
+ from fractal_server.app.schemas.v2.task_group import TaskGroupActivityStatusV2
17
+ from fractal_server.logger import set_logger
18
+ from fractal_server.ssh._fabric import FractalSSH
19
+ from fractal_server.tasks.utils import get_log_path
20
+ from fractal_server.tasks.v2.utils_background import get_current_log
21
+ from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
22
+ from fractal_server.utils import get_timestamp
23
+
24
+ LOGGER_NAME = __name__
25
+
26
+
27
+ def deactivate_ssh(
28
+ *,
29
+ task_group_activity_id: int,
30
+ task_group_id: int,
31
+ fractal_ssh: FractalSSH,
32
+ tasks_base_dir: str,
33
+ ) -> None:
34
+ """
35
+ Deactivate a task group venv.
36
+
37
+ This function is run as a background task, therefore exceptions must be
38
+ handled.
39
+
40
+ Arguments:
41
+ task_group_id:
42
+ task_group_activity_id:
43
+ fractal_ssh:
44
+ tasks_base_dir:
45
+ Only used as a `safe_root` in `remove_dir`, and typically set to
46
+ `user_settings.ssh_tasks_dir`.
47
+ """
48
+
49
+ with TemporaryDirectory() as tmpdir:
50
+ log_file_path = get_log_path(Path(tmpdir))
51
+ logger = set_logger(
52
+ logger_name=LOGGER_NAME,
53
+ log_file_path=log_file_path,
54
+ )
55
+
56
+ with next(get_sync_db()) as db:
57
+
58
+ # Get main objects from db
59
+ activity = db.get(TaskGroupActivityV2, task_group_activity_id)
60
+ task_group = db.get(TaskGroupV2, task_group_id)
61
+ if activity is None or task_group is None:
62
+ # Use `logging` directly
63
+ logging.error(
64
+ "Cannot find database rows with "
65
+ f"{task_group_id=} and {task_group_activity_id=}:\n"
66
+ f"{task_group=}\n{activity=}. Exit."
67
+ )
68
+ return
69
+
70
+ # Log some info
71
+ logger.debug("START")
72
+ for key, value in task_group.model_dump().items():
73
+ logger.debug(f"task_group.{key}: {value}")
74
+
75
+ # Check that SSH connection works
76
+ try:
77
+ fractal_ssh.check_connection()
78
+ except Exception as e:
79
+ logger.error("Cannot establish SSH connection.")
80
+ fail_and_cleanup(
81
+ task_group=task_group,
82
+ task_group_activity=activity,
83
+ logger_name=LOGGER_NAME,
84
+ log_file_path=log_file_path,
85
+ exception=e,
86
+ db=db,
87
+ )
88
+ return
89
+
90
+ # Check that the (local) task_group venv_path does exist
91
+ if not fractal_ssh.remote_exists(task_group.venv_path):
92
+ error_msg = f"{task_group.venv_path} does not exist."
93
+ logger.error(error_msg)
94
+ fail_and_cleanup(
95
+ task_group=task_group,
96
+ task_group_activity=activity,
97
+ logger_name=LOGGER_NAME,
98
+ log_file_path=log_file_path,
99
+ exception=FileNotFoundError(error_msg),
100
+ db=db,
101
+ )
102
+ return
103
+
104
+ try:
105
+
106
+ activity.status = TaskGroupActivityStatusV2.ONGOING
107
+ activity = add_commit_refresh(obj=activity, db=db)
108
+
109
+ if task_group.pip_freeze is None:
110
+ logger.warning(
111
+ "Recreate pip-freeze information, since "
112
+ f"{task_group.pip_freeze=}. NOTE: this should only "
113
+ "happen for task groups created before 2.9.0."
114
+ )
115
+
116
+ # Prepare replacements for templates
117
+ replacements = get_collection_replacements(
118
+ task_group=task_group,
119
+ python_bin="/not/applicable",
120
+ )
121
+
122
+ # Prepare arguments for `_customize_and_run_template`
123
+ script_dir_remote = (
124
+ Path(task_group.path) / SCRIPTS_SUBFOLDER
125
+ ).as_posix()
126
+ common_args = dict(
127
+ replacements=replacements,
128
+ script_dir_local=(
129
+ Path(tmpdir) / SCRIPTS_SUBFOLDER
130
+ ).as_posix(),
131
+ script_dir_remote=script_dir_remote,
132
+ prefix=(
133
+ f"{int(time.time())}_"
134
+ f"{TaskGroupActivityActionV2.DEACTIVATE}"
135
+ ),
136
+ fractal_ssh=fractal_ssh,
137
+ logger_name=LOGGER_NAME,
138
+ )
139
+
140
+ # Run `pip freeze`
141
+ pip_freeze_stdout = _customize_and_run_template(
142
+ template_filename="3_pip_freeze.sh",
143
+ **common_args,
144
+ )
145
+
146
+ # Update pip-freeze data
147
+ logger.info("Add pip freeze stdout to TaskGroupV2 - start")
148
+ activity.log = get_current_log(log_file_path)
149
+ activity = add_commit_refresh(obj=activity, db=db)
150
+ task_group.pip_freeze = pip_freeze_stdout
151
+ task_group = add_commit_refresh(obj=task_group, db=db)
152
+ logger.info("Add pip freeze stdout to TaskGroupV2 - end")
153
+
154
+ # Handle some specific cases for wheel-file case
155
+ if task_group.origin == TaskGroupV2OriginEnum.WHEELFILE:
156
+
157
+ logger.info(
158
+ f"Handle specific cases for {task_group.origin=}."
159
+ )
160
+
161
+ # Blocking situation: `wheel_path` is not set or points
162
+ # to a missing path
163
+ if (
164
+ task_group.wheel_path is None
165
+ or not fractal_ssh.remote_exists(task_group.wheel_path)
166
+ ):
167
+ error_msg = (
168
+ "Invalid wheel path for task group with "
169
+ f"{task_group_id=}. {task_group.wheel_path=} is "
170
+ "unset or does not exist."
171
+ )
172
+ logger.error(error_msg)
173
+ fail_and_cleanup(
174
+ task_group=task_group,
175
+ task_group_activity=activity,
176
+ logger_name=LOGGER_NAME,
177
+ log_file_path=log_file_path,
178
+ exception=FileNotFoundError(error_msg),
179
+ db=db,
180
+ )
181
+ return
182
+
183
+ # Recoverable situation: `wheel_path` was not yet copied
184
+ # over to the correct server-side folder
185
+ wheel_path_parent_dir = Path(task_group.wheel_path).parent
186
+ if wheel_path_parent_dir != Path(task_group.path):
187
+ logger.warning(
188
+ f"{wheel_path_parent_dir.as_posix()} differs from "
189
+ f"{task_group.path}. NOTE: this should only "
190
+ "happen for task groups created before 2.9.0."
191
+ )
192
+
193
+ if task_group.wheel_path not in task_group.pip_freeze:
194
+ raise ValueError(
195
+ f"Cannot find {task_group.wheel_path=} in "
196
+ "pip-freeze data. Exit."
197
+ )
198
+
199
+ logger.info(
200
+ f"Now copy wheel file into {task_group.path}."
201
+ )
202
+ new_wheel_path = _copy_wheel_file_ssh(
203
+ task_group=task_group,
204
+ fractal_ssh=fractal_ssh,
205
+ logger_name=LOGGER_NAME,
206
+ )
207
+ logger.info(f"Copied wheel file to {new_wheel_path}.")
208
+
209
+ task_group.wheel_path = new_wheel_path
210
+ new_pip_freeze = task_group.pip_freeze.replace(
211
+ task_group.wheel_path,
212
+ new_wheel_path,
213
+ )
214
+ task_group.pip_freeze = new_pip_freeze
215
+ task_group = add_commit_refresh(obj=task_group, db=db)
216
+ logger.info(
217
+ "Updated `wheel_path` and `pip_freeze` "
218
+ "task-group attributes."
219
+ )
220
+
221
+ # We now have all required information for reactivating the
222
+ # virtual environment at a later point
223
+ logger.info(f"Now removing {task_group.venv_path}.")
224
+ fractal_ssh.remove_folder(
225
+ folder=task_group.venv_path,
226
+ safe_root=tasks_base_dir,
227
+ )
228
+ logger.info(f"All good, {task_group.venv_path} removed.")
229
+ activity.status = TaskGroupActivityStatusV2.OK
230
+ activity.log = get_current_log(log_file_path)
231
+ activity.timestamp_ended = get_timestamp()
232
+ activity = add_commit_refresh(obj=activity, db=db)
233
+
234
+ except Exception as e:
235
+ fail_and_cleanup(
236
+ task_group=task_group,
237
+ task_group_activity=activity,
238
+ logger_name=LOGGER_NAME,
239
+ log_file_path=log_file_path,
240
+ exception=e,
241
+ db=db,
242
+ )
243
+ return
@@ -1,2 +1,199 @@
1
- def reactivate_ssh():
2
- pass
1
+ import logging
2
+ import time
3
+ from pathlib import Path
4
+ from tempfile import TemporaryDirectory
5
+
6
+ from ..utils_background import add_commit_refresh
7
+ from ..utils_background import fail_and_cleanup
8
+ from ..utils_templates import get_collection_replacements
9
+ from ._utils import _customize_and_run_template
10
+ from fractal_server.app.db import get_sync_db
11
+ from fractal_server.app.models.v2 import TaskGroupActivityV2
12
+ from fractal_server.app.models.v2 import TaskGroupV2
13
+ from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
14
+ from fractal_server.app.schemas.v2.task_group import TaskGroupActivityStatusV2
15
+ from fractal_server.logger import set_logger
16
+ from fractal_server.ssh._fabric import FractalSSH
17
+ from fractal_server.tasks.utils import get_log_path
18
+ from fractal_server.tasks.v2.utils_background import get_current_log
19
+ from fractal_server.tasks.v2.utils_python_interpreter import (
20
+ get_python_interpreter_v2,
21
+ )
22
+ from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
23
+ from fractal_server.utils import get_timestamp
24
+
25
+ LOGGER_NAME = __name__
26
+
27
+
28
+ def reactivate_ssh(
29
+ *,
30
+ task_group_activity_id: int,
31
+ task_group_id: int,
32
+ fractal_ssh: FractalSSH,
33
+ tasks_base_dir: str,
34
+ ) -> None:
35
+ """
36
+ Reactivate a task group venv.
37
+
38
+ This function is run as a background task, therefore exceptions must be
39
+ handled.
40
+
41
+ Arguments:
42
+ task_group_id:
43
+ task_group_activity_id:
44
+ fractal_ssh:
45
+ tasks_base_dir:
46
+ Only used as a `safe_root` in `remove_dir`, and typically set to
47
+ `user_settings.ssh_tasks_dir`.
48
+ """
49
+
50
+ with TemporaryDirectory() as tmpdir:
51
+ log_file_path = get_log_path(Path(tmpdir))
52
+ logger = set_logger(
53
+ logger_name=LOGGER_NAME,
54
+ log_file_path=log_file_path,
55
+ )
56
+
57
+ with next(get_sync_db()) as db:
58
+
59
+ # Get main objects from db
60
+ activity = db.get(TaskGroupActivityV2, task_group_activity_id)
61
+ task_group = db.get(TaskGroupV2, task_group_id)
62
+ if activity is None or task_group is None:
63
+ # Use `logging` directly
64
+ logging.error(
65
+ "Cannot find database rows with "
66
+ f"{task_group_id=} and {task_group_activity_id=}:\n"
67
+ f"{task_group=}\n{activity=}. Exit."
68
+ )
69
+ return
70
+
71
+ # Log some info
72
+ logger.debug("START")
73
+ for key, value in task_group.model_dump().items():
74
+ logger.debug(f"task_group.{key}: {value}")
75
+
76
+ # Check that SSH connection works
77
+ try:
78
+ fractal_ssh.check_connection()
79
+ except Exception as e:
80
+ logger.error("Cannot establish SSH connection.")
81
+ fail_and_cleanup(
82
+ task_group=task_group,
83
+ task_group_activity=activity,
84
+ logger_name=LOGGER_NAME,
85
+ log_file_path=log_file_path,
86
+ exception=e,
87
+ db=db,
88
+ )
89
+ return
90
+
91
+ # Check that the (remote) task_group venv_path does not exist
92
+ if fractal_ssh.remote_exists(task_group.venv_path):
93
+ error_msg = f"{task_group.venv_path} already exists."
94
+ logger.error(error_msg)
95
+ fail_and_cleanup(
96
+ task_group=task_group,
97
+ task_group_activity=activity,
98
+ logger_name=LOGGER_NAME,
99
+ log_file_path=log_file_path,
100
+ exception=FileExistsError(error_msg),
101
+ db=db,
102
+ )
103
+ return
104
+
105
+ try:
106
+ activity.status = TaskGroupActivityStatusV2.ONGOING
107
+ activity = add_commit_refresh(obj=activity, db=db)
108
+
109
+ # Prepare replacements for templates
110
+ replacements = get_collection_replacements(
111
+ task_group=task_group,
112
+ python_bin=get_python_interpreter_v2(
113
+ python_version=task_group.python_version
114
+ ),
115
+ )
116
+
117
+ # Prepare replacements for templates
118
+ pip_freeze_file_local = f"{tmpdir}/pip_freeze.txt"
119
+ pip_freeze_file_remote = (
120
+ Path(task_group.path) / "_tmp_pip_freeze.txt"
121
+ ).as_posix()
122
+ with open(pip_freeze_file_local, "w") as f:
123
+ f.write(task_group.pip_freeze)
124
+ fractal_ssh.send_file(
125
+ local=pip_freeze_file_local, remote=pip_freeze_file_remote
126
+ )
127
+ replacements.append(
128
+ ("__PIP_FREEZE_FILE__", pip_freeze_file_remote)
129
+ )
130
+
131
+ # Prepare common arguments for `_customize_and_run_template``
132
+ script_dir_remote = (
133
+ Path(task_group.path) / SCRIPTS_SUBFOLDER
134
+ ).as_posix()
135
+ common_args = dict(
136
+ replacements=replacements,
137
+ script_dir_local=(
138
+ Path(tmpdir) / SCRIPTS_SUBFOLDER
139
+ ).as_posix(),
140
+ script_dir_remote=script_dir_remote,
141
+ prefix=(
142
+ f"{int(time.time())}_"
143
+ f"{TaskGroupActivityActionV2.REACTIVATE}"
144
+ ),
145
+ fractal_ssh=fractal_ssh,
146
+ logger_name=LOGGER_NAME,
147
+ )
148
+
149
+ # Create remote directory for scripts
150
+ fractal_ssh.mkdir(folder=script_dir_remote)
151
+
152
+ logger.debug("start - create venv")
153
+ _customize_and_run_template(
154
+ template_filename="1_create_venv.sh",
155
+ **common_args,
156
+ )
157
+ logger.debug("end - create venv")
158
+ activity.log = get_current_log(log_file_path)
159
+ activity.timestamp_ended = get_timestamp()
160
+ activity = add_commit_refresh(obj=activity, db=db)
161
+
162
+ logger.debug("start - install from pip freeze")
163
+ _customize_and_run_template(
164
+ template_filename="6_pip_install_from_freeze.sh",
165
+ **common_args,
166
+ )
167
+ logger.debug("end - install from pip freeze")
168
+ activity.log = get_current_log(log_file_path)
169
+ activity.status = TaskGroupActivityStatusV2.OK
170
+ activity.timestamp_ended = get_timestamp()
171
+ activity = add_commit_refresh(obj=activity, db=db)
172
+ task_group.active = True
173
+ task_group = add_commit_refresh(obj=task_group, db=db)
174
+ logger.debug("END")
175
+
176
+ except Exception as reactivate_e:
177
+ # Delete corrupted venv_path
178
+ try:
179
+ logger.info(f"Now delete folder {task_group.venv_path}")
180
+ fractal_ssh.remove_folder(
181
+ folder=task_group.venv_path,
182
+ safe_root=tasks_base_dir,
183
+ )
184
+ logger.info(f"Deleted folder {task_group.venv_path}")
185
+ except Exception as rm_e:
186
+ logger.error(
187
+ "Removing folder failed.\n"
188
+ f"Original error:\n{str(rm_e)}"
189
+ )
190
+
191
+ fail_and_cleanup(
192
+ task_group=task_group,
193
+ task_group_activity=activity,
194
+ logger_name=LOGGER_NAME,
195
+ log_file_path=log_file_path,
196
+ exception=reactivate_e,
197
+ db=db,
198
+ )
199
+ return
@@ -119,30 +119,6 @@ def _prepare_tasks_metadata(
119
119
  return task_list
120
120
 
121
121
 
122
- def check_task_files_exist(task_list: list[TaskCreateV2]) -> None:
123
- """
124
- Check that the modules listed in task commands point to existing files.
125
-
126
- Args:
127
- task_list:
128
- """
129
- for _task in task_list:
130
- if _task.command_non_parallel is not None:
131
- _task_path = _task.command_non_parallel.split()[1]
132
- if not Path(_task_path).exists():
133
- raise FileNotFoundError(
134
- f"Task `{_task.name}` has `command_non_parallel` "
135
- f"pointing to missing file `{_task_path}`."
136
- )
137
- if _task.command_parallel is not None:
138
- _task_path = _task.command_parallel.split()[1]
139
- if not Path(_task_path).exists():
140
- raise FileNotFoundError(
141
- f"Task `{_task.name}` has `command_parallel` "
142
- f"pointing to missing file `{_task_path}`."
143
- )
144
-
145
-
146
122
  def get_current_log(logger_file_path: str) -> str:
147
123
  with open(logger_file_path, "r") as f:
148
124
  return f.read()
@@ -122,9 +122,26 @@ def _zip_folder_to_file_and_remove(folder: str) -> None:
122
122
  3. Checks if the folder can be safely deleted using the
123
123
  `_folder_can_be_deleted` function. If so, deletes the original folder.
124
124
  """
125
- _create_zip(folder, f"{folder}_tmp.zip")
126
- shutil.move(f"{folder}_tmp.zip", f"{folder}.zip")
125
+
126
+ tmp_zipfile = f"{folder}_tmp.zip"
127
+ zipfile = f"{folder}.zip"
128
+
129
+ try:
130
+ logger.info(f"Start creating temporary zip file at '{tmp_zipfile}'.")
131
+ _create_zip(folder, tmp_zipfile)
132
+ logger.info("Zip file created.")
133
+ except Exception as e:
134
+ logger.error(
135
+ f"Error while creating temporary zip file. Original error: '{e}'."
136
+ )
137
+ Path(tmp_zipfile).unlink(missing_ok=True)
138
+ return
139
+
140
+ logger.info(f"Moving temporary zip file to {zipfile}.")
141
+ shutil.move(tmp_zipfile, zipfile)
142
+ logger.info("Zip file moved.")
143
+
127
144
  if _folder_can_be_deleted(folder):
128
- logger.info(f"Deleting folder '{folder}'...")
145
+ logger.info(f"Removing folder '{folder}'.")
129
146
  shutil.rmtree(folder)
130
- logger.info(f"Deletion of folder '{folder}' completed.")
147
+ logger.info("Folder removed.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fractal-server
3
- Version: 2.9.0a2
3
+ Version: 2.9.0a3
4
4
  Summary: Server component of the Fractal analytics platform
5
5
  Home-page: https://github.com/fractal-analytics-platform/fractal-server
6
6
  License: BSD-3-Clause
@@ -1,4 +1,4 @@
1
- fractal_server/__init__.py,sha256=d0Y48obcx61ZaWA7ZS1o1kdZRZ6sJMhUfuKDOT5xPKU,24
1
+ fractal_server/__init__.py,sha256=8LdiYD77VYwRKrVoavHTgYHkexYzgn-F5OpNKKf-WMU,24
2
2
  fractal_server/__main__.py,sha256=dEkCfzLLQrIlxsGC-HBfoR-RBMWnJDgNrxYTyzmE9c0,6146
3
3
  fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
4
4
  fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -52,10 +52,10 @@ fractal_server/app/routes/api/v2/project.py,sha256=eWYFJ7F2ZYQcpi-_n-rhPF-Q4gJhz
52
52
  fractal_server/app/routes/api/v2/status.py,sha256=6N9DSZ4iFqbZImorWfEAPoyoFUgEruo4Hweqo0x0xXU,6435
53
53
  fractal_server/app/routes/api/v2/submit.py,sha256=jqYix7X6dUJn8w6RWasu1lOFf7T_CcXQlpVY38njE24,8688
54
54
  fractal_server/app/routes/api/v2/task.py,sha256=K0ik33t7vL8BAK5S7fqyJDNdRK4stGqb_73bSa8tvPE,7159
55
- fractal_server/app/routes/api/v2/task_collection.py,sha256=-DVhultvdI3Jh8Jq8W5np6Lnkh5oisjbKCwxFmwddmo,9820
55
+ fractal_server/app/routes/api/v2/task_collection.py,sha256=_K7c-83jpmaLz1gXM_MbndskkmV6oo_ivg0mWP6hfvs,9621
56
56
  fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=cctW61-C2QYF2KXluS15lLhZJS_kt30Ca6UGLFO32z0,6207
57
57
  fractal_server/app/routes/api/v2/task_group.py,sha256=Ove7Vr3p8GKtskHANAZh8TWwOw8dfbFCN2UQFv6DhqM,8136
58
- fractal_server/app/routes/api/v2/task_group_lifecycle.py,sha256=UODAv1kk8qgT7wvcVKK6eRs-fEBw3T1pPwmT6VkkAn4,7404
58
+ fractal_server/app/routes/api/v2/task_group_lifecycle.py,sha256=SPUB-QHvADaJm3zyuPIyCg-TqbHujwiwr7RauvYGXJ0,8972
59
59
  fractal_server/app/routes/api/v2/workflow.py,sha256=vjCNRzMHaAB4YWbAEWGlELHXDN4GjtE26IkIiB15RGM,8682
60
60
  fractal_server/app/routes/api/v2/workflow_import.py,sha256=WJST1AZypvOTGUrjhomYVh4R2ow8RoGpuwzNiq81Pzc,10971
61
61
  fractal_server/app/routes/api/v2/workflowtask.py,sha256=ciHTwXXFiFnMF7ZpJ3Xs0q6YfuZrFvIjqndlzAEdZpo,6969
@@ -134,31 +134,31 @@ fractal_server/app/runner/v2/runner_functions_low_level.py,sha256=1fWvQ6YZUUnDhO
134
134
  fractal_server/app/runner/v2/task_interface.py,sha256=hT3p-bRGsLNAR_dNv_PYFoqzIF_EQtSsGwl38j1haYA,1824
135
135
  fractal_server/app/runner/versions.py,sha256=dSaPRWqmFPHjg20kTCHmi_dmGNcCETflDtDLronNanU,852
136
136
  fractal_server/app/schemas/__init__.py,sha256=stURAU_t3AOBaH0HSUbV-GKhlPKngnnIMoqWc3orFyI,135
137
- fractal_server/app/schemas/_validators.py,sha256=Ft-Ueol_rFwGjEebKVK2bUELm7w7bFG8MOpTamlY8Fs,3503
137
+ fractal_server/app/schemas/_validators.py,sha256=T5EswIJAJRvawfzqWtPcN2INAfiBXyE4m0iwQm4ht-0,3149
138
138
  fractal_server/app/schemas/user.py,sha256=aUD8YAcfYTEO06TEUoTx4heVrXFiX7E2Mb8D2--4FsA,2130
139
139
  fractal_server/app/schemas/user_group.py,sha256=YwJvYgj-PI66LWy38CEd_FIZPsBV1_2N5zJPGFcFvBw,2143
140
140
  fractal_server/app/schemas/user_settings.py,sha256=TalISeEfCrtN8LgqbLx1Q8ZPoeiZnbksg5NYAVzkIqY,3527
141
141
  fractal_server/app/schemas/v1/__init__.py,sha256=CrBGgBhoemCvmZ70ZUchM-jfVAICnoa7AjZBAtL2UB0,1852
142
- fractal_server/app/schemas/v1/applyworkflow.py,sha256=uuIh7fHlHEL4yLqL-dePI6-nfCsqgBYATmht7w_KITw,4302
143
- fractal_server/app/schemas/v1/dataset.py,sha256=n71lNUO3JLy2K3IM9BZM2Fk1EnKQOTU7pm2s2rJ1FGY,3444
142
+ fractal_server/app/schemas/v1/applyworkflow.py,sha256=dYArxQAOBdUIEXX_Ejz8b9fBhEYu1nMm6b_Z6_P6TgA,4052
143
+ fractal_server/app/schemas/v1/dataset.py,sha256=DWFCxZjApcKt2M6UJMK0tmejXwUT09vjUULf2D7Y-f0,3293
144
144
  fractal_server/app/schemas/v1/dumps.py,sha256=67VXnyLh_0Ufo7rPM2jZ9P9rk0CnYcVAkilx_cLX6sg,1274
145
145
  fractal_server/app/schemas/v1/manifest.py,sha256=Yht7guhs0Pcl2U0RMOCbI_UHBZ9YO_YU0H8hxACx3TY,3829
146
- fractal_server/app/schemas/v1/project.py,sha256=TO2TjI4m9FO-A9IB9lUCld7E4Ld0k4MacLcyA9j6Qi4,1218
147
- fractal_server/app/schemas/v1/state.py,sha256=GYeOE_1PtDOgu5W4t_3gw3DBHXH2aCGzINRs2dp8_h4,472
146
+ fractal_server/app/schemas/v1/project.py,sha256=Zxd-AguQQG9z2CfJ_sJh5SB9WcHPFbWpLgP_AhjOyZs,1067
147
+ fractal_server/app/schemas/v1/state.py,sha256=tBXzp_qW2TNNNPBo-AWEaffEU-1GkMBtUoaMgiN_EL0,302
148
148
  fractal_server/app/schemas/v1/task.py,sha256=7BxOZ_qoRQ8n3YbQpDvB7VMcxB5fSYQmR5RLIWhuJ5U,3704
149
149
  fractal_server/app/schemas/v1/task_collection.py,sha256=uvq9bcMaGD_qHsh7YtcpoSAkVAbw12eY4DocIO3MKOg,3057
150
- fractal_server/app/schemas/v1/workflow.py,sha256=tuOs5E5Q_ozA8if7YPZ07cQjzqB_QMkBS4u92qo4Ro0,4618
150
+ fractal_server/app/schemas/v1/workflow.py,sha256=oRKamLSuAgrTcv3gMMxGcotDloLL2c3NNgPA39UEmmM,4467
151
151
  fractal_server/app/schemas/v2/__init__.py,sha256=dzDsAzLLMgyQBa3b64n71K1u83tbBn9_Oloaqqv1tjA,2444
152
- fractal_server/app/schemas/v2/dataset.py,sha256=Jipcj9LiOOipAeM2Ew113wuNQ6CrbC1nf3KwnNApBco,2638
152
+ fractal_server/app/schemas/v2/dataset.py,sha256=zRlcO0wDZahTW1PINdVEuARZ7GZUuEqqop7UdE3-5do,2470
153
153
  fractal_server/app/schemas/v2/dumps.py,sha256=s6dg-pHZFui6t2Ktm0SMxjKDN-v-ZqBHz9iTsBQF3eU,1712
154
- fractal_server/app/schemas/v2/job.py,sha256=oYSLYkQ0HL83QyjEGIaggtZ117FndzFlONMKWd9sTXM,3270
154
+ fractal_server/app/schemas/v2/job.py,sha256=42V-bFfMvysRplwTKGsL_WshAVsWSM6yjFqypxwrY3k,3020
155
155
  fractal_server/app/schemas/v2/manifest.py,sha256=Uqtd7DbyOkf9bxBOKkU7Sv7nToBIFGUcfjY7rd5iO7c,6981
156
- fractal_server/app/schemas/v2/project.py,sha256=UXEA0UUUe0bFFOVLLmVtvDFLBO5vmD1JVI7EeTIcwDo,756
156
+ fractal_server/app/schemas/v2/project.py,sha256=ABv9LSLVCq1QYthEhBvZOTn_4DFEC-7cH28tFGFdM7I,589
157
157
  fractal_server/app/schemas/v2/status.py,sha256=SQaUpQkjFq5c5k5J4rOjNhuQaDOEg8lksPhkKmPU5VU,332
158
158
  fractal_server/app/schemas/v2/task.py,sha256=FFAbYwDlqowB8gVMdjFVPVHvAM0T89PYLixUth49xfQ,6870
159
159
  fractal_server/app/schemas/v2/task_collection.py,sha256=yHpCRxoj6tKqCiQfUjaTj8SfCn1ChD_P6okfEOzyUDE,6518
160
160
  fractal_server/app/schemas/v2/task_group.py,sha256=e4NwFuOmiO0afoZLVsI_XHIpD_o_QWDpzlI7ZoNAYwo,3014
161
- fractal_server/app/schemas/v2/workflow.py,sha256=HSNQSrBRdoBzh8Igr76FUWCAWvVzykrqmUv1vGv-8og,2026
161
+ fractal_server/app/schemas/v2/workflow.py,sha256=-KWvXnbHBFA3pj5n7mfSyLKJQSqkJmoziIEe7mpLl3M,1875
162
162
  fractal_server/app/schemas/v2/workflowtask.py,sha256=vDdMktYbHeYBgB5OuWSv6wRPRXWqvetkeqQ7IC5YtfA,5751
163
163
  fractal_server/app/security/__init__.py,sha256=8Xd4GxumZgvxEH1Vli3ULehwdesEPiaAbtffJvAEgNo,12509
164
164
  fractal_server/app/user_settings.py,sha256=aZgQ3i0JkHfgwLGW1ee6Gzr1ae3IioFfJKKSsSS8Svk,1312
@@ -202,7 +202,7 @@ fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py
202
202
  fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py,sha256=9BwqUS9Gf7UW_KjrzHbtViC880qhD452KAytkHWWZyk,746
203
203
  fractal_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
204
204
  fractal_server/ssh/__init__.py,sha256=sVUmzxf7_DuXG1xoLQ1_00fo5NPhi2LJipSmU5EAkPs,124
205
- fractal_server/ssh/_fabric.py,sha256=Nwsc5uU2BcOE7t1AFFpytKUsZHfbQxB0uKF5HD-dycA,21647
205
+ fractal_server/ssh/_fabric.py,sha256=O1Dxl6xlg9pvqdKyKqy18mYQlslJoJHapXtDMCzZcBA,21711
206
206
  fractal_server/string_tools.py,sha256=XtMNsr5R7GmgzmFi68zkKMedHs8vjGoVMMCXqWhIk9k,2568
207
207
  fractal_server/syringe.py,sha256=3qSMW3YaMKKnLdgnooAINOPxnCOxP7y2jeAQYB21Gdo,2786
208
208
  fractal_server/tasks/__init__.py,sha256=kadmVUoIghl8s190_Tt-8f-WBqMi8u8oU4Pvw39NHE8,23
@@ -215,30 +215,31 @@ fractal_server/tasks/v1/get_collection_data.py,sha256=5C22jp356rCH5IIC0J57wOu-DC
215
215
  fractal_server/tasks/v1/utils.py,sha256=HYFyNAyZofmf--mVgdwGC5TJpGShIWIDaS01yRr4HxM,1771
216
216
  fractal_server/tasks/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
217
217
  fractal_server/tasks/v2/local/__init__.py,sha256=9RVItnS7OyLsJOuJjWMCicaky4ASUPQEYD4SzDs0hOE,141
218
- fractal_server/tasks/v2/local/collect.py,sha256=kTgMDiOX9qVDncmrgieOfYE71jqFwBpDgO1cbD5ZQVQ,12199
219
- fractal_server/tasks/v2/local/deactivate.py,sha256=PyJm3PoQrYOBCm0F6p6xD7Gx9Tfnn_YZlXY-j3Fi6pM,8975
220
- fractal_server/tasks/v2/local/reactivate.py,sha256=eTSrEoZ54_6ExviozxRTgjNsrESG9PbQW-TiGqNMJcA,6117
221
- fractal_server/tasks/v2/local/utils_local.py,sha256=JHHiS_SvPLSezxGWSWslMhLEWmMp9PepnzePXV6r0e8,1521
222
- fractal_server/tasks/v2/ssh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
223
- fractal_server/tasks/v2/ssh/collect.py,sha256=j50euINjxGS4S5-aYTIWwV34xydKvT6W5P9Vxv5TMuk,15263
224
- fractal_server/tasks/v2/ssh/deactivate.py,sha256=fwBCtj-8I_8s8Zd-nyQX7YaTjoPOK1pn07Tznvjbv8Y,31
225
- fractal_server/tasks/v2/ssh/reactivate.py,sha256=0pqK-g5uyykCkLsjYDVr8QTEBrIB31XQXvi0MQfqv-w,31
218
+ fractal_server/tasks/v2/local/_utils.py,sha256=EvhmVwYjqaNyDCUMEsTWYOUXLgEwR1xr6bu32apCEI8,2491
219
+ fractal_server/tasks/v2/local/collect.py,sha256=BbXSgxExPUxFxcmBs3ejwWzRae-sQgfbk3zZkAQg77Y,12190
220
+ fractal_server/tasks/v2/local/deactivate.py,sha256=Tjooode2oXfaSLiJz7iSu-qC1OkeIJtOQeUvUo4K4Ts,8970
221
+ fractal_server/tasks/v2/local/reactivate.py,sha256=R3rArAzUpMGf6xa3dGVwwXHW9WVDi5ia28AFisZsqNc,6112
222
+ fractal_server/tasks/v2/ssh/__init__.py,sha256=aSQbVi6Ummt9QzcSLWNmSqYjfdxrn9ROmqgH6bDpI7k,135
223
+ fractal_server/tasks/v2/ssh/_utils.py,sha256=2E-F_862zM6FZA-im-E8t8kjptWRIhBj1IDHC6QD1H8,2818
224
+ fractal_server/tasks/v2/ssh/collect.py,sha256=U8f6-dgtAI9Pr-gWPeDMeYdOkxYSqfsrYcSPDYEqz4I,12942
225
+ fractal_server/tasks/v2/ssh/deactivate.py,sha256=YNJEcUPNuQ7H3I9EtWn84es2AY9Dnij388w6PQra4wc,10186
226
+ fractal_server/tasks/v2/ssh/reactivate.py,sha256=brIa3XuoD7g9m9vIB7CSgog6KC9w8e45phLT-vSSXlI,7754
226
227
  fractal_server/tasks/v2/templates/1_create_venv.sh,sha256=PK0jdHKtQpda1zULebBaVPORt4t6V17wa4N1ohcj5ac,548
227
228
  fractal_server/tasks/v2/templates/2_pip_install.sh,sha256=RDDfbFnGOK3aRuHyXqDOUNCGullzAr0zS7BFqG1CJeE,1720
228
229
  fractal_server/tasks/v2/templates/3_pip_freeze.sh,sha256=JldREScEBI4cD_qjfX4UK7V4aI-FnX9ZvVNxgpSOBFc,168
229
230
  fractal_server/tasks/v2/templates/4_pip_show.sh,sha256=84NGHlg6JIbrQktgGKyfGsggPFzy6RBJuOmIpPUhsrw,1747
230
231
  fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh,sha256=q-6ZUvA6w6FDVEoSd9O63LaJ9tKZc7qAFH72SGPrd_k,284
231
232
  fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh,sha256=n9C8w76YraLbeTe7NhuLzvAQiJCm_akL3Mc3EMfxrHo,1007
232
- fractal_server/tasks/v2/utils_background.py,sha256=nAnel85xWIWPJjlv1XtRBJ59zoSjufvLobU8GlPRWUI,5155
233
+ fractal_server/tasks/v2/utils_background.py,sha256=tikXhggqxdU7EnKdx2co3UwinlDazEjfOPQOXtO58zs,4240
233
234
  fractal_server/tasks/v2/utils_database.py,sha256=6r56yyFPnEBrXl6ncmO6D76znzISQCFZqCYcD-Ummd4,1213
234
235
  fractal_server/tasks/v2/utils_package_names.py,sha256=RDg__xrvQs4ieeVzmVdMcEh95vGQYrv9Hfal-5EDBM8,2393
235
236
  fractal_server/tasks/v2/utils_python_interpreter.py,sha256=-EWh3Y3VqHLDOWUO_wG_wknqmGqKAD0O2KTLhNjrZaI,948
236
237
  fractal_server/tasks/v2/utils_templates.py,sha256=C5WLuY3uGG2s53OEL-__H35-fmSlguwZx836BPFHBpE,2732
237
238
  fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
238
239
  fractal_server/utils.py,sha256=utvmBx8K9I8hRWFquxna2pBaOqe0JifDL_NVPmihEJI,3525
239
- fractal_server/zip_tools.py,sha256=xYpzBshysD2nmxkD5WLYqMzPYUcCRM3kYy-7n9bJL-U,4426
240
- fractal_server-2.9.0a2.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
241
- fractal_server-2.9.0a2.dist-info/METADATA,sha256=Qk7nGzAcl_hZRCJp8pdCV2PQACAWwqOzTCH7nFW__pw,4585
242
- fractal_server-2.9.0a2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
243
- fractal_server-2.9.0a2.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
244
- fractal_server-2.9.0a2.dist-info/RECORD,,
240
+ fractal_server/zip_tools.py,sha256=GjDgo_sf6V_DDg6wWeBlZu5zypIxycn_l257p_YVKGc,4876
241
+ fractal_server-2.9.0a3.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
242
+ fractal_server-2.9.0a3.dist-info/METADATA,sha256=W92NgqXaqh1eO8X_o9XPN8chROYn0ZyszQCjkBo_BIg,4585
243
+ fractal_server-2.9.0a3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
244
+ fractal_server-2.9.0a3.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
245
+ fractal_server-2.9.0a3.dist-info/RECORD,,