fractal-server 2.8.1__py3-none-any.whl → 2.9.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/models/v2/__init__.py +3 -3
  3. fractal_server/app/models/v2/task.py +0 -72
  4. fractal_server/app/models/v2/task_group.py +102 -0
  5. fractal_server/app/routes/admin/v1.py +1 -20
  6. fractal_server/app/routes/admin/v2/job.py +1 -20
  7. fractal_server/app/routes/admin/v2/task_group.py +53 -13
  8. fractal_server/app/routes/api/v2/__init__.py +11 -2
  9. fractal_server/app/routes/api/v2/{_aux_functions_task_collection.py → _aux_functions_task_lifecycle.py} +43 -0
  10. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +21 -14
  11. fractal_server/app/routes/api/v2/task_collection.py +26 -51
  12. fractal_server/app/routes/api/v2/task_collection_custom.py +3 -3
  13. fractal_server/app/routes/api/v2/task_group.py +83 -14
  14. fractal_server/app/routes/api/v2/task_group_lifecycle.py +221 -0
  15. fractal_server/app/routes/api/v2/workflow.py +1 -1
  16. fractal_server/app/routes/api/v2/workflow_import.py +2 -2
  17. fractal_server/app/routes/aux/_timestamp.py +25 -0
  18. fractal_server/app/schemas/v2/__init__.py +3 -2
  19. fractal_server/app/schemas/v2/task_collection.py +0 -21
  20. fractal_server/app/schemas/v2/task_group.py +30 -6
  21. fractal_server/migrations/versions/3082479ac4ea_taskgroup_activity_and_venv_info_to_.py +105 -0
  22. fractal_server/ssh/_fabric.py +18 -0
  23. fractal_server/tasks/utils.py +2 -12
  24. fractal_server/tasks/v2/local/__init__.py +3 -0
  25. fractal_server/tasks/v2/local/collect.py +291 -0
  26. fractal_server/tasks/v2/local/deactivate.py +162 -0
  27. fractal_server/tasks/v2/local/reactivate.py +159 -0
  28. fractal_server/tasks/v2/local/utils_local.py +52 -0
  29. fractal_server/tasks/v2/ssh/__init__.py +0 -0
  30. fractal_server/tasks/v2/ssh/collect.py +387 -0
  31. fractal_server/tasks/v2/ssh/deactivate.py +2 -0
  32. fractal_server/tasks/v2/ssh/reactivate.py +2 -0
  33. fractal_server/tasks/v2/templates/{_2_preliminary_pip_operations.sh → 1_create_venv.sh} +6 -7
  34. fractal_server/tasks/v2/templates/{_3_pip_install.sh → 2_pip_install.sh} +8 -1
  35. fractal_server/tasks/v2/templates/{_4_pip_freeze.sh → 3_pip_freeze.sh} +0 -7
  36. fractal_server/tasks/v2/templates/{_5_pip_show.sh → 4_pip_show.sh} +5 -6
  37. fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +10 -0
  38. fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +35 -0
  39. fractal_server/tasks/v2/utils_background.py +42 -103
  40. fractal_server/tasks/v2/utils_templates.py +32 -2
  41. fractal_server/utils.py +4 -2
  42. {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/METADATA +2 -2
  43. {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/RECORD +47 -36
  44. fractal_server/app/models/v2/collection_state.py +0 -22
  45. fractal_server/tasks/v2/collection_local.py +0 -357
  46. fractal_server/tasks/v2/collection_ssh.py +0 -352
  47. fractal_server/tasks/v2/templates/_1_create_venv.sh +0 -42
  48. /fractal_server/tasks/v2/{database_operations.py → utils_database.py} +0 -0
  49. {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/LICENSE +0 -0
  50. {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/WHEEL +0 -0
  51. {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,159 @@
1
+ import logging
2
+ import shutil
3
+ import time
4
+ from pathlib import Path
5
+ from tempfile import TemporaryDirectory
6
+
7
+ from ..utils_background import add_commit_refresh
8
+ from ..utils_background import fail_and_cleanup
9
+ from ..utils_templates import get_collection_replacements
10
+ from .utils_local 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.task_group import TaskGroupActivityStatusV2
16
+ from fractal_server.logger import set_logger
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
+
26
+ LOGGER_NAME = __name__
27
+
28
+
29
+ def reactivate_local(
30
+ *,
31
+ task_group_activity_id: int,
32
+ task_group_id: int,
33
+ ) -> None:
34
+ """
35
+ Reactivate 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
+ """
44
+
45
+ with TemporaryDirectory() as tmpdir:
46
+ log_file_path = get_log_path(Path(tmpdir))
47
+ logger = set_logger(
48
+ logger_name=LOGGER_NAME,
49
+ log_file_path=log_file_path,
50
+ )
51
+
52
+ with next(get_sync_db()) as db:
53
+
54
+ # Get main objects from db
55
+ activity = db.get(TaskGroupActivityV2, task_group_activity_id)
56
+ task_group = db.get(TaskGroupV2, task_group_id)
57
+ if activity is None or task_group is None:
58
+ # Use `logging` directly
59
+ logging.error(
60
+ "Cannot find database rows with "
61
+ f"{task_group_id=} and {task_group_activity_id=}:\n"
62
+ f"{task_group=}\n{activity=}. Exit."
63
+ )
64
+ return
65
+
66
+ # Log some info
67
+ logger.debug("START")
68
+
69
+ for key, value in task_group.model_dump().items():
70
+ logger.debug(f"task_group.{key}: {value}")
71
+
72
+ # Check that the (local) task_group venv_path does not exist
73
+ if Path(task_group.venv_path).exists():
74
+ error_msg = f"{task_group.venv_path} already exists."
75
+ logger.error(error_msg)
76
+ fail_and_cleanup(
77
+ task_group=task_group,
78
+ task_group_activity=activity,
79
+ logger_name=LOGGER_NAME,
80
+ log_file_path=log_file_path,
81
+ exception=FileExistsError(error_msg),
82
+ db=db,
83
+ )
84
+ return
85
+
86
+ try:
87
+ activity.status = TaskGroupActivityStatusV2.ONGOING
88
+ activity = add_commit_refresh(obj=activity, db=db)
89
+
90
+ # Prepare replacements for templates
91
+ replacements = get_collection_replacements(
92
+ task_group=task_group,
93
+ python_bin=get_python_interpreter_v2(
94
+ python_version=task_group.python_version
95
+ ),
96
+ )
97
+ with open(f"{tmpdir}/pip_freeze.txt", "w") as f:
98
+ f.write(task_group.pip_freeze)
99
+ replacements.append(
100
+ ("__PIP_FREEZE_FILE__", f"{tmpdir}/pip_freeze.txt")
101
+ )
102
+ # Prepare common arguments for `_customize_and_run_template``
103
+ common_args = dict(
104
+ replacements=replacements,
105
+ script_dir=(
106
+ Path(task_group.path) / SCRIPTS_SUBFOLDER
107
+ ).as_posix(),
108
+ prefix=(
109
+ f"{int(time.time())}_"
110
+ f"{TaskGroupActivityActionV2.REACTIVATE}_"
111
+ ),
112
+ logger_name=LOGGER_NAME,
113
+ )
114
+
115
+ logger.debug("start - create venv")
116
+ _customize_and_run_template(
117
+ template_filename="1_create_venv.sh",
118
+ **common_args,
119
+ )
120
+ logger.debug("end - create venv")
121
+ activity.log = get_current_log(log_file_path)
122
+ activity.timestamp_ended = get_timestamp()
123
+ activity = add_commit_refresh(obj=activity, db=db)
124
+
125
+ logger.debug("start - install from pip freeze")
126
+ _customize_and_run_template(
127
+ template_filename="6_pip_install_from_freeze.sh",
128
+ **common_args,
129
+ )
130
+ logger.debug("end - install from pip freeze")
131
+ activity.log = get_current_log(log_file_path)
132
+ activity.status = TaskGroupActivityStatusV2.OK
133
+ activity.timestamp_ended = get_timestamp()
134
+ activity = add_commit_refresh(obj=activity, db=db)
135
+ task_group.active = True
136
+ task_group = add_commit_refresh(obj=task_group, db=db)
137
+ logger.debug("END")
138
+
139
+ except Exception as reactivate_e:
140
+ # Delete corrupted venv_path
141
+ try:
142
+ logger.info(f"Now delete folder {task_group.venv_path}")
143
+ shutil.rmtree(task_group.venv_path)
144
+ logger.info(f"Deleted folder {task_group.venv_path}")
145
+ except Exception as rm_e:
146
+ logger.error(
147
+ "Removing folder failed.\n"
148
+ f"Original error:\n{str(rm_e)}"
149
+ )
150
+
151
+ fail_and_cleanup(
152
+ task_group=task_group,
153
+ task_group_activity=activity,
154
+ logger_name=LOGGER_NAME,
155
+ log_file_path=log_file_path,
156
+ exception=reactivate_e,
157
+ db=db,
158
+ )
159
+ return
@@ -0,0 +1,52 @@
1
+ from pathlib import Path
2
+ from typing import Optional
3
+
4
+ from fractal_server.logger import get_logger
5
+ from fractal_server.tasks.v2.utils_templates import customize_template
6
+ from fractal_server.utils import execute_command_sync
7
+
8
+
9
+ def _customize_and_run_template(
10
+ template_filename: str,
11
+ replacements: list[tuple[str, str]],
12
+ script_dir: str,
13
+ logger_name: str,
14
+ prefix: Optional[int] = None,
15
+ ) -> str:
16
+ """
17
+ Customize one of the template bash scripts.
18
+
19
+ Args:
20
+ template_filename: Filename of the template file (ends with ".sh").
21
+ replacements: Dictionary of replacements.
22
+ script_dir: Local folder where the script will be placed.
23
+ prefix: Prefix for the script filename.
24
+ """
25
+ logger = get_logger(logger_name=logger_name)
26
+ logger.debug(f"_customize_and_run_template {template_filename} - START")
27
+
28
+ # Prepare name and path of script
29
+ if not template_filename.endswith(".sh"):
30
+ raise ValueError(
31
+ f"Invalid {template_filename=} (it must end with '.sh')."
32
+ )
33
+
34
+ template_filename_stripped = template_filename[:-3]
35
+
36
+ if prefix is not None:
37
+ script_filename = f"{prefix}{template_filename_stripped}"
38
+ else:
39
+ script_filename = template_filename_stripped
40
+ script_path_local = Path(script_dir) / script_filename
41
+ # Read template
42
+ customize_template(
43
+ template_name=template_filename,
44
+ replacements=replacements,
45
+ script_path=script_path_local,
46
+ )
47
+ cmd = f"bash {script_path_local}"
48
+ logger.debug(f"Now run '{cmd}' ")
49
+ stdout = execute_command_sync(command=cmd, logger_name=logger_name)
50
+ logger.debug(f"Standard output of '{cmd}':\n{stdout}")
51
+ logger.debug(f"_customize_and_run_template {template_filename} - END")
52
+ return stdout
File without changes
@@ -0,0 +1,387 @@
1
+ import logging
2
+ import os
3
+ import time
4
+ from pathlib import Path
5
+ from tempfile import TemporaryDirectory
6
+
7
+ from ..utils_background import _prepare_tasks_metadata
8
+ from ..utils_background import fail_and_cleanup
9
+ from ..utils_database import create_db_tasks_and_update_task_group
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 import TaskGroupActivityStatusV2
15
+ from fractal_server.app.schemas.v2.manifest import ManifestV2
16
+ from fractal_server.logger import get_logger
17
+ from fractal_server.logger import set_logger
18
+ from fractal_server.ssh._fabric import FractalSSH
19
+ from fractal_server.tasks.v2.utils_background import add_commit_refresh
20
+ from fractal_server.tasks.v2.utils_background import get_current_log
21
+ from fractal_server.tasks.v2.utils_package_names import compare_package_names
22
+ from fractal_server.tasks.v2.utils_python_interpreter import (
23
+ get_python_interpreter_v2,
24
+ )
25
+ from fractal_server.tasks.v2.utils_templates import customize_template
26
+ from fractal_server.tasks.v2.utils_templates import get_collection_replacements
27
+ from fractal_server.tasks.v2.utils_templates import (
28
+ parse_script_pip_show_stdout,
29
+ )
30
+ from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
31
+ from fractal_server.utils import get_timestamp
32
+
33
+ LOGGER_NAME = __name__
34
+
35
+
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
+ template_filename_stripped = Path(template_filename).stem
67
+ script_filename = f"{prefix}{template_filename_stripped}"
68
+ script_path_local = Path(script_dir_local) / script_filename
69
+
70
+ customize_template(
71
+ template_name=template_filename,
72
+ replacements=replacements,
73
+ script_path=script_path_local,
74
+ )
75
+
76
+ # Transfer script to remote host
77
+ script_path_remote = os.path.join(
78
+ script_dir_remote,
79
+ script_filename,
80
+ )
81
+ logger.debug(f"Now transfer {script_path_local=} over SSH.")
82
+ fractal_ssh.send_file(
83
+ local=script_path_local,
84
+ remote=script_path_remote,
85
+ )
86
+
87
+ # Execute script remotely
88
+ cmd = f"bash {script_path_remote}"
89
+ logger.debug(f"Now run '{cmd}' over SSH.")
90
+ stdout = fractal_ssh.run_command(cmd=cmd)
91
+ logger.debug(f"Standard output of '{cmd}':\n{stdout}")
92
+
93
+ logger.debug(f"_customize_and_run_template {template_filename} - END")
94
+ return stdout
95
+
96
+
97
+ def _copy_wheel_file_ssh(
98
+ task_group: TaskGroupV2, fractal_ssh: FractalSSH
99
+ ) -> str:
100
+ logger = get_logger(LOGGER_NAME)
101
+ source = task_group.wheel_path
102
+ dest = (
103
+ Path(task_group.path) / Path(task_group.wheel_path).name
104
+ ).as_posix()
105
+ cmd = f"cp {source} {dest}"
106
+ logger.debug(f"[_copy_wheel_file] START {source=} {dest=}")
107
+ fractal_ssh.run_command(cmd=cmd)
108
+ logger.debug(f"[_copy_wheel_file] END {source=} {dest=}")
109
+ return dest
110
+
111
+
112
+ def collect_package_ssh(
113
+ *,
114
+ task_group_id: int,
115
+ task_group_activity_id: int,
116
+ fractal_ssh: FractalSSH,
117
+ tasks_base_dir: str,
118
+ ) -> None:
119
+ """
120
+ Collect a task package over SSH
121
+
122
+ This function is run as a background task, therefore exceptions must be
123
+ handled.
124
+
125
+ NOTE: by making this function sync, it runs within a thread - due to
126
+ starlette/fastapi handling of background tasks (see
127
+ https://github.com/encode/starlette/blob/master/starlette/background.py).
128
+
129
+
130
+ Arguments:
131
+ task_group_id:
132
+ task_group_activity_id:
133
+ fractal_ssh:
134
+ tasks_base_dir:
135
+ Only used as a `safe_root` in `remove_dir`, and typically set to
136
+ `user_settings.ssh_tasks_dir`.
137
+ """
138
+
139
+ # Work within a temporary folder, where also logs will be placed
140
+ with TemporaryDirectory() as tmpdir:
141
+ LOGGER_NAME = "task_collection_ssh"
142
+ log_file_path = Path(tmpdir) / "log"
143
+ logger = set_logger(
144
+ logger_name=LOGGER_NAME,
145
+ log_file_path=log_file_path,
146
+ )
147
+
148
+ with next(get_sync_db()) as db:
149
+
150
+ # Get main objects from db
151
+ activity = db.get(TaskGroupActivityV2, task_group_activity_id)
152
+ task_group = db.get(TaskGroupV2, task_group_id)
153
+ if activity is None or task_group is None:
154
+ # Use `logging` directly
155
+ logging.error(
156
+ "Cannot find database rows with "
157
+ f"{task_group_id=} and {task_group_activity_id=}:\n"
158
+ f"{task_group=}\n{activity=}. Exit."
159
+ )
160
+ return
161
+
162
+ # Log some info
163
+ logger.debug("START")
164
+ for key, value in task_group.model_dump().items():
165
+ logger.debug(f"task_group.{key}: {value}")
166
+
167
+ # Check that SSH connection works
168
+ try:
169
+ fractal_ssh.check_connection()
170
+ except Exception as e:
171
+ logger.error("Cannot establish SSH connection.")
172
+ fail_and_cleanup(
173
+ task_group=task_group,
174
+ task_group_activity=activity,
175
+ logger_name=LOGGER_NAME,
176
+ log_file_path=log_file_path,
177
+ exception=e,
178
+ db=db,
179
+ )
180
+ return
181
+
182
+ # Check that the (remote) task_group path does not exist
183
+ if fractal_ssh.remote_exists(task_group.path):
184
+ error_msg = f"{task_group.path} already exists."
185
+ logger.error(error_msg)
186
+ fail_and_cleanup(
187
+ task_group=task_group,
188
+ task_group_activity=activity,
189
+ logger_name=LOGGER_NAME,
190
+ log_file_path=log_file_path,
191
+ exception=FileExistsError(error_msg),
192
+ db=db,
193
+ )
194
+ return
195
+
196
+ try:
197
+
198
+ # Prepare replacements for templates
199
+ replacements = get_collection_replacements(
200
+ task_group=task_group,
201
+ python_bin=get_python_interpreter_v2(
202
+ python_version=task_group.python_version
203
+ ),
204
+ )
205
+
206
+ # Prepare common arguments for `_customize_and_run_template``
207
+ script_dir_remote = (
208
+ Path(task_group.path) / SCRIPTS_SUBFOLDER
209
+ ).as_posix()
210
+ common_args = dict(
211
+ replacements=replacements,
212
+ script_dir_local=(
213
+ Path(tmpdir) / SCRIPTS_SUBFOLDER
214
+ ).as_posix(),
215
+ script_dir_remote=script_dir_remote,
216
+ prefix=(
217
+ f"{int(time.time())}_"
218
+ f"{TaskGroupActivityActionV2.COLLECT}_"
219
+ ),
220
+ fractal_ssh=fractal_ssh,
221
+ logger_name=LOGGER_NAME,
222
+ )
223
+
224
+ # Create remote `task_group.path` and `script_dir_remote`
225
+ # folders (note that because of `parents=True` we are in
226
+ # the `no error if existing, make parent directories as
227
+ # needed` scenario for `mkdir`)
228
+ fractal_ssh.mkdir(folder=task_group.path, parents=True)
229
+ fractal_ssh.mkdir(folder=script_dir_remote, parents=True)
230
+
231
+ # Copy wheel file into task group path
232
+ if task_group.wheel_path:
233
+ new_wheel_path = _copy_wheel_file_ssh(
234
+ task_group=task_group,
235
+ fractal_ssh=fractal_ssh,
236
+ )
237
+ task_group.wheel_path = new_wheel_path
238
+ task_group = add_commit_refresh(obj=task_group, db=db)
239
+
240
+ logger.debug("installing - START")
241
+
242
+ # Set status to ONGOING and refresh logs
243
+ activity.status = TaskGroupActivityStatusV2.ONGOING
244
+ activity.log = get_current_log(log_file_path)
245
+ activity = add_commit_refresh(obj=activity, db=db)
246
+
247
+ # Run script 1
248
+ stdout = _customize_and_run_template(
249
+ template_filename="1_create_venv.sh",
250
+ **common_args,
251
+ )
252
+ activity.log = get_current_log(log_file_path)
253
+ activity = add_commit_refresh(obj=activity, db=db)
254
+
255
+ # Run script 2
256
+ stdout = _customize_and_run_template(
257
+ template_filename="2_pip_install.sh",
258
+ **common_args,
259
+ )
260
+ activity.log = get_current_log(log_file_path)
261
+ activity = add_commit_refresh(obj=activity, db=db)
262
+
263
+ # Run script 3
264
+ pip_freeze_stdout = _customize_and_run_template(
265
+ template_filename="3_pip_freeze.sh",
266
+ **common_args,
267
+ )
268
+ activity.log = get_current_log(log_file_path)
269
+ activity = add_commit_refresh(obj=activity, db=db)
270
+
271
+ # Run script 4
272
+ stdout = _customize_and_run_template(
273
+ template_filename="4_pip_show.sh",
274
+ **common_args,
275
+ )
276
+ activity.log = get_current_log(log_file_path)
277
+ activity = add_commit_refresh(obj=activity, db=db)
278
+
279
+ # Run script 5
280
+ venv_info = _customize_and_run_template(
281
+ template_filename="5_get_venv_size_and_file_number.sh",
282
+ **common_args,
283
+ )
284
+ venv_size, venv_file_number = venv_info.split()
285
+ activity.log = get_current_log(log_file_path)
286
+ activity = add_commit_refresh(obj=activity, db=db)
287
+
288
+ pkg_attrs = parse_script_pip_show_stdout(stdout)
289
+
290
+ for key, value in pkg_attrs.items():
291
+ logger.debug(f"parsed from pip-show: {key}={value}")
292
+ # Check package_name match between pip show and task-group
293
+ package_name_pip_show = pkg_attrs.get("package_name")
294
+ package_name_task_group = task_group.pkg_name
295
+ compare_package_names(
296
+ pkg_name_pip_show=package_name_pip_show,
297
+ pkg_name_task_group=package_name_task_group,
298
+ logger_name=LOGGER_NAME,
299
+ )
300
+ # Extract/drop parsed attributes
301
+ package_name = package_name_task_group
302
+ python_bin = pkg_attrs.pop("python_bin")
303
+ package_root_parent_remote = pkg_attrs.pop(
304
+ "package_root_parent"
305
+ )
306
+ manifest_path_remote = pkg_attrs.pop("manifest_path")
307
+
308
+ # TODO SSH: Use more robust logic to determine `package_root`.
309
+ # Examples: use `importlib.util.find_spec`, or parse the output
310
+ # of `pip show --files {package_name}`.
311
+ package_name_underscore = package_name.replace("-", "_")
312
+ package_root_remote = (
313
+ Path(package_root_parent_remote) / package_name_underscore
314
+ ).as_posix()
315
+
316
+ # Read and validate remote manifest file
317
+ pkg_manifest_dict = fractal_ssh.read_remote_json_file(
318
+ manifest_path_remote
319
+ )
320
+ logger.info(f"Loaded {manifest_path_remote=}")
321
+ pkg_manifest = ManifestV2(**pkg_manifest_dict)
322
+ logger.info("Manifest is a valid ManifestV2")
323
+
324
+ logger.info("_prepare_tasks_metadata - start")
325
+ task_list = _prepare_tasks_metadata(
326
+ package_manifest=pkg_manifest,
327
+ package_version=task_group.version,
328
+ package_root=Path(package_root_remote),
329
+ python_bin=Path(python_bin),
330
+ )
331
+ logger.info("_prepare_tasks_metadata - end")
332
+
333
+ logger.info("create_db_tasks_and_update_task_group - " "start")
334
+ create_db_tasks_and_update_task_group(
335
+ task_list=task_list,
336
+ task_group_id=task_group.id,
337
+ db=db,
338
+ )
339
+ logger.info("create_db_tasks_and_update_task_group - end")
340
+
341
+ # Update task_group data
342
+ logger.info(
343
+ "Add pip_freeze, venv_size and venv_file_number "
344
+ "to TaskGroupV2 - start"
345
+ )
346
+ task_group.pip_freeze = pip_freeze_stdout
347
+ task_group.venv_size_in_kB = int(venv_size)
348
+ task_group.venv_file_number = int(venv_file_number)
349
+ task_group = add_commit_refresh(obj=task_group, db=db)
350
+ logger.info(
351
+ "Add pip_freeze, venv_size and venv_file_number "
352
+ "to TaskGroupV2 - end"
353
+ )
354
+
355
+ # Finalize (write metadata to DB)
356
+ logger.debug("finalising - START")
357
+ activity.status = TaskGroupActivityStatusV2.OK
358
+ activity.timestamp_ended = get_timestamp()
359
+ activity = add_commit_refresh(obj=activity, db=db)
360
+ logger.debug("finalising - END")
361
+ logger.debug("END")
362
+
363
+ logger.debug("END")
364
+
365
+ except Exception as collection_e:
366
+ # Delete corrupted package dir
367
+ try:
368
+ logger.info(f"Now delete remote folder {task_group.path}")
369
+ fractal_ssh.remove_folder(
370
+ folder=task_group.path,
371
+ safe_root=tasks_base_dir,
372
+ )
373
+ logger.info(f"Deleted remoted folder {task_group.path}")
374
+ except Exception as e_rm:
375
+ logger.error(
376
+ "Removing folder failed. "
377
+ f"Original error:\n{str(e_rm)}"
378
+ )
379
+ fail_and_cleanup(
380
+ task_group=task_group,
381
+ task_group_activity=activity,
382
+ log_file_path=log_file_path,
383
+ logger_name=LOGGER_NAME,
384
+ exception=collection_e,
385
+ db=db,
386
+ )
387
+ return
@@ -0,0 +1,2 @@
1
+ def deactivate_ssh():
2
+ pass
@@ -0,0 +1,2 @@
1
+ def reactivate_ssh():
2
+ pass
@@ -5,18 +5,17 @@ write_log(){
5
5
  echo "[collect-task, $TIMESTAMP] $1"
6
6
  }
7
7
 
8
+
8
9
  # Variables to be filled within fractal-server
9
10
  PACKAGE_ENV_DIR=__PACKAGE_ENV_DIR__
11
+ PYTHON=__PYTHON__
10
12
 
11
13
  TIME_START=$(date +%s)
12
14
 
13
- VENVPYTHON=${PACKAGE_ENV_DIR}/bin/python
14
-
15
- # Upgrade pip
16
- write_log "START upgrade pip"
17
- "$VENVPYTHON" -m pip install --no-cache-dir "pip<=__FRACTAL_MAX_PIP_VERSION__" --upgrade
18
- "$VENVPYTHON" -m pip install --no-cache-dir setuptools
19
- write_log "END upgrade pip"
15
+ # Create venv
16
+ write_log "START create venv in ${PACKAGE_ENV_DIR}"
17
+ "$PYTHON" -m venv "$PACKAGE_ENV_DIR" --copies
18
+ write_log "END create venv in ${PACKAGE_ENV_DIR}"
20
19
  echo
21
20
 
22
21
  # End
@@ -5,16 +5,23 @@ write_log(){
5
5
  echo "[collect-task, $TIMESTAMP] $1"
6
6
  }
7
7
 
8
-
9
8
  # Variables to be filled within fractal-server
10
9
  PACKAGE_ENV_DIR=__PACKAGE_ENV_DIR__
11
10
  INSTALL_STRING=__INSTALL_STRING__
12
11
  PINNED_PACKAGE_LIST="__PINNED_PACKAGE_LIST__"
12
+ FRACTAL_MAX_PIP_VERSION="__FRACTAL_MAX_PIP_VERSION__"
13
13
 
14
14
  TIME_START=$(date +%s)
15
15
 
16
16
  VENVPYTHON=${PACKAGE_ENV_DIR}/bin/python
17
17
 
18
+ # Upgrade `pip` and install `setuptools`
19
+ write_log "START upgrade pip and install setuptools"
20
+ "$VENVPYTHON" -m pip install --no-cache-dir "pip<=${FRACTAL_MAX_PIP_VERSION}" --upgrade
21
+ "$VENVPYTHON" -m pip install --no-cache-dir setuptools
22
+ write_log "END upgrade pip and install setuptools"
23
+ echo
24
+
18
25
  # Install package
19
26
  write_log "START install ${INSTALL_STRING}"
20
27
  "$VENVPYTHON" -m pip install --no-cache-dir "$INSTALL_STRING"
@@ -1,12 +1,5 @@
1
1
  set -e
2
2
 
3
- write_log(){
4
- TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
5
- echo "[collect-task, $TIMESTAMP] $1"
6
- }
7
-
8
-
9
-
10
3
  # Variables to be filled within fractal-server
11
4
  PACKAGE_ENV_DIR=__PACKAGE_ENV_DIR__
12
5