fractal-server 2.2.0a0__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fractal_server/__init__.py +1 -1
- fractal_server/app/db/__init__.py +1 -1
- fractal_server/app/models/v1/state.py +1 -2
- fractal_server/app/routes/admin/v1.py +2 -2
- fractal_server/app/routes/admin/v2.py +2 -2
- fractal_server/app/routes/api/v1/job.py +2 -2
- fractal_server/app/routes/api/v1/task_collection.py +4 -4
- fractal_server/app/routes/api/v2/__init__.py +23 -3
- fractal_server/app/routes/api/v2/job.py +2 -2
- fractal_server/app/routes/api/v2/submit.py +6 -0
- fractal_server/app/routes/api/v2/task_collection.py +74 -34
- fractal_server/app/routes/api/v2/task_collection_custom.py +170 -0
- fractal_server/app/routes/api/v2/task_collection_ssh.py +125 -0
- fractal_server/app/routes/aux/_runner.py +10 -2
- fractal_server/app/runner/compress_folder.py +120 -0
- fractal_server/app/runner/executors/slurm/__init__.py +0 -3
- fractal_server/app/runner/executors/slurm/_batching.py +0 -1
- fractal_server/app/runner/executors/slurm/_slurm_config.py +9 -9
- fractal_server/app/runner/executors/slurm/ssh/__init__.py +3 -0
- fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py +112 -0
- fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py +120 -0
- fractal_server/app/runner/executors/slurm/ssh/executor.py +1488 -0
- fractal_server/app/runner/executors/slurm/sudo/__init__.py +3 -0
- fractal_server/app/runner/executors/slurm/{_check_jobs_status.py → sudo/_check_jobs_status.py} +1 -1
- fractal_server/app/runner/executors/slurm/{_executor_wait_thread.py → sudo/_executor_wait_thread.py} +1 -1
- fractal_server/app/runner/executors/slurm/{_subprocess_run_as_user.py → sudo/_subprocess_run_as_user.py} +1 -1
- fractal_server/app/runner/executors/slurm/{executor.py → sudo/executor.py} +12 -12
- fractal_server/app/runner/extract_archive.py +38 -0
- fractal_server/app/runner/v1/__init__.py +78 -40
- fractal_server/app/runner/v1/_slurm/__init__.py +1 -1
- fractal_server/app/runner/v2/__init__.py +183 -82
- fractal_server/app/runner/v2/_local_experimental/__init__.py +22 -12
- fractal_server/app/runner/v2/_local_experimental/executor.py +12 -8
- fractal_server/app/runner/v2/_slurm/__init__.py +1 -6
- fractal_server/app/runner/v2/_slurm_ssh/__init__.py +125 -0
- fractal_server/app/runner/v2/_slurm_ssh/_submit_setup.py +83 -0
- fractal_server/app/runner/v2/_slurm_ssh/get_slurm_config.py +182 -0
- fractal_server/app/runner/v2/runner_functions_low_level.py +9 -11
- fractal_server/app/runner/versions.py +30 -0
- fractal_server/app/schemas/v1/__init__.py +1 -0
- fractal_server/app/schemas/{state.py → v1/state.py} +4 -21
- fractal_server/app/schemas/v2/__init__.py +4 -1
- fractal_server/app/schemas/v2/task_collection.py +101 -30
- fractal_server/config.py +222 -21
- fractal_server/main.py +27 -1
- fractal_server/migrations/env.py +1 -1
- fractal_server/ssh/__init__.py +4 -0
- fractal_server/ssh/_fabric.py +245 -0
- fractal_server/tasks/utils.py +12 -64
- fractal_server/tasks/v1/background_operations.py +2 -2
- fractal_server/tasks/{endpoint_operations.py → v1/endpoint_operations.py} +7 -12
- fractal_server/tasks/v1/utils.py +67 -0
- fractal_server/tasks/v2/_TaskCollectPip.py +61 -32
- fractal_server/tasks/v2/_venv_pip.py +195 -0
- fractal_server/tasks/v2/background_operations.py +257 -295
- fractal_server/tasks/v2/background_operations_ssh.py +317 -0
- fractal_server/tasks/v2/endpoint_operations.py +136 -0
- fractal_server/tasks/v2/templates/_1_create_venv.sh +46 -0
- fractal_server/tasks/v2/templates/_2_upgrade_pip.sh +30 -0
- fractal_server/tasks/v2/templates/_3_pip_install.sh +32 -0
- fractal_server/tasks/v2/templates/_4_pip_freeze.sh +21 -0
- fractal_server/tasks/v2/templates/_5_pip_show.sh +59 -0
- fractal_server/tasks/v2/utils.py +54 -0
- {fractal_server-2.2.0a0.dist-info → fractal_server-2.3.0.dist-info}/METADATA +6 -2
- {fractal_server-2.2.0a0.dist-info → fractal_server-2.3.0.dist-info}/RECORD +68 -44
- fractal_server/tasks/v2/get_collection_data.py +0 -14
- {fractal_server-2.2.0a0.dist-info → fractal_server-2.3.0.dist-info}/LICENSE +0 -0
- {fractal_server-2.2.0a0.dist-info → fractal_server-2.3.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.2.0a0.dist-info → fractal_server-2.3.0.dist-info}/entry_points.txt +0 -0
@@ -5,288 +5,230 @@ is used as a background task for the task-collection endpoint.
|
|
5
5
|
import json
|
6
6
|
from pathlib import Path
|
7
7
|
from shutil import rmtree as shell_rmtree
|
8
|
+
from typing import Optional
|
8
9
|
|
9
|
-
from
|
10
|
-
from
|
10
|
+
from sqlalchemy.orm import Session as DBSyncSession
|
11
|
+
from sqlalchemy.orm.attributes import flag_modified
|
12
|
+
|
13
|
+
from ..utils import get_absolute_venv_path
|
14
|
+
from ..utils import get_collection_freeze
|
11
15
|
from ..utils import get_collection_log
|
12
16
|
from ..utils import get_collection_path
|
13
17
|
from ..utils import get_log_path
|
14
18
|
from ..utils import slugify_task_name
|
15
19
|
from ._TaskCollectPip import _TaskCollectPip
|
16
|
-
from fractal_server.app.db import DBSyncSession
|
17
20
|
from fractal_server.app.db import get_sync_db
|
18
21
|
from fractal_server.app.models.v2 import CollectionStateV2
|
19
22
|
from fractal_server.app.models.v2 import TaskV2
|
20
|
-
from fractal_server.app.schemas.v2 import
|
23
|
+
from fractal_server.app.schemas.v2 import CollectionStatusV2
|
21
24
|
from fractal_server.app.schemas.v2 import TaskCreateV2
|
22
25
|
from fractal_server.app.schemas.v2 import TaskReadV2
|
26
|
+
from fractal_server.app.schemas.v2.manifest import ManifestV2
|
23
27
|
from fractal_server.logger import get_logger
|
24
28
|
from fractal_server.logger import reset_logger_handlers
|
25
29
|
from fractal_server.logger import set_logger
|
26
|
-
from fractal_server.
|
30
|
+
from fractal_server.tasks.v2._venv_pip import _create_venv_install_package_pip
|
27
31
|
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def _get_task_type(task: TaskCreateV2) -> str:
|
34
|
+
if task.command_non_parallel is None:
|
35
|
+
return "parallel"
|
36
|
+
elif task.command_parallel is None:
|
37
|
+
return "non_parallel"
|
38
|
+
else:
|
39
|
+
return "compound"
|
36
40
|
|
37
|
-
Args:
|
38
|
-
venv_path:
|
39
|
-
task_pkg:
|
40
|
-
logger_name:
|
41
41
|
|
42
|
-
|
43
|
-
|
42
|
+
def _insert_tasks(
|
43
|
+
task_list: list[TaskCreateV2],
|
44
|
+
db: DBSyncSession,
|
45
|
+
owner: Optional[str] = None,
|
46
|
+
) -> list[TaskV2]:
|
47
|
+
"""
|
48
|
+
Insert tasks into database
|
44
49
|
"""
|
45
50
|
|
46
|
-
|
47
|
-
|
48
|
-
pip = venv_path / "venv/bin/pip"
|
49
|
-
|
50
|
-
extras = f"[{task_pkg.package_extras}]" if task_pkg.package_extras else ""
|
51
|
+
owner_dict = dict(owner=owner) if owner is not None else dict()
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
53
|
+
task_db_list = [
|
54
|
+
TaskV2(**t.dict(), **owner_dict, type=_get_task_type(t))
|
55
|
+
for t in task_list
|
56
|
+
]
|
57
|
+
db.add_all(task_db_list)
|
58
|
+
db.commit()
|
59
|
+
for t in task_db_list:
|
60
|
+
db.refresh(t)
|
61
|
+
db.close()
|
62
|
+
return task_db_list
|
62
63
|
|
63
|
-
await execute_command(
|
64
|
-
cwd=venv_path,
|
65
|
-
command=f"{pip} install --upgrade pip",
|
66
|
-
logger_name=logger_name,
|
67
|
-
)
|
68
|
-
await execute_command(
|
69
|
-
cwd=venv_path, command=cmd_install, logger_name=logger_name
|
70
|
-
)
|
71
|
-
if task_pkg.pinned_package_versions:
|
72
|
-
for (
|
73
|
-
pinned_pkg_name,
|
74
|
-
pinned_pkg_version,
|
75
|
-
) in task_pkg.pinned_package_versions.items():
|
76
|
-
|
77
|
-
logger.debug(
|
78
|
-
"Specific version required: "
|
79
|
-
f"{pinned_pkg_name}=={pinned_pkg_version}"
|
80
|
-
)
|
81
|
-
logger.debug(
|
82
|
-
"Preliminary check: verify that "
|
83
|
-
f"{pinned_pkg_version} is already installed"
|
84
|
-
)
|
85
|
-
stdout_inspect = await execute_command(
|
86
|
-
cwd=venv_path,
|
87
|
-
command=f"{pip} show {pinned_pkg_name}",
|
88
|
-
logger_name=logger_name,
|
89
|
-
)
|
90
|
-
current_version = next(
|
91
|
-
line.split()[-1]
|
92
|
-
for line in stdout_inspect.split("\n")
|
93
|
-
if line.startswith("Version:")
|
94
|
-
)
|
95
|
-
if current_version != pinned_pkg_version:
|
96
|
-
logger.debug(
|
97
|
-
f"Currently installed version of {pinned_pkg_name} "
|
98
|
-
f"({current_version}) differs from pinned version "
|
99
|
-
f"({pinned_pkg_version}); "
|
100
|
-
f"install version {pinned_pkg_version}."
|
101
|
-
)
|
102
|
-
await execute_command(
|
103
|
-
cwd=venv_path,
|
104
|
-
command=(
|
105
|
-
f"{pip} install "
|
106
|
-
f"{pinned_pkg_name}=={pinned_pkg_version}"
|
107
|
-
),
|
108
|
-
logger_name=logger_name,
|
109
|
-
)
|
110
|
-
else:
|
111
|
-
logger.debug(
|
112
|
-
f"Currently installed version of {pinned_pkg_name} "
|
113
|
-
f"({current_version}) already matches the pinned version."
|
114
|
-
)
|
115
64
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
65
|
+
def _set_collection_state_data_status(
|
66
|
+
*,
|
67
|
+
state_id: int,
|
68
|
+
new_status: CollectionStatusV2,
|
69
|
+
logger_name: str,
|
70
|
+
db: DBSyncSession,
|
71
|
+
):
|
72
|
+
logger = get_logger(logger_name)
|
73
|
+
logger.debug(f"{state_id=} - set state.data['status'] to {new_status}")
|
74
|
+
collection_state = db.get(CollectionStateV2, state_id)
|
75
|
+
collection_state.data["status"] = CollectionStatusV2(new_status)
|
76
|
+
flag_modified(collection_state, "data")
|
77
|
+
db.commit()
|
120
78
|
|
121
|
-
location = Path(
|
122
|
-
next(
|
123
|
-
line.split()[-1]
|
124
|
-
for line in stdout_inspect.split("\n")
|
125
|
-
if line.startswith("Location:")
|
126
|
-
)
|
127
|
-
)
|
128
79
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
if not package_root.exists():
|
143
|
-
raise RuntimeError(
|
144
|
-
"Could not determine package installation location."
|
145
|
-
)
|
146
|
-
return package_root
|
80
|
+
def _set_collection_state_data_log(
|
81
|
+
*,
|
82
|
+
state_id: int,
|
83
|
+
new_log: str,
|
84
|
+
logger_name: str,
|
85
|
+
db: DBSyncSession,
|
86
|
+
):
|
87
|
+
logger = get_logger(logger_name)
|
88
|
+
logger.debug(f"{state_id=} - set state.data['log']")
|
89
|
+
collection_state = db.get(CollectionStateV2, state_id)
|
90
|
+
collection_state.data["log"] = new_log
|
91
|
+
flag_modified(collection_state, "data")
|
92
|
+
db.commit()
|
147
93
|
|
148
94
|
|
149
|
-
|
95
|
+
def _set_collection_state_data_info(
|
150
96
|
*,
|
151
|
-
|
152
|
-
|
97
|
+
state_id: int,
|
98
|
+
new_info: str,
|
153
99
|
logger_name: str,
|
154
|
-
|
155
|
-
|
100
|
+
db: DBSyncSession,
|
101
|
+
):
|
102
|
+
logger = get_logger(logger_name)
|
103
|
+
logger.debug(f"{state_id=} - set state.data['info']")
|
104
|
+
collection_state = db.get(CollectionStateV2, state_id)
|
105
|
+
collection_state.data["info"] = new_info
|
106
|
+
flag_modified(collection_state, "data")
|
107
|
+
db.commit()
|
156
108
|
|
157
|
-
Args:
|
158
|
-
path: the directory in which to create the environment
|
159
|
-
task_pkg: object containing the different metadata required to install
|
160
|
-
the package
|
161
109
|
|
162
|
-
|
163
|
-
|
164
|
-
|
110
|
+
def _handle_failure(
|
111
|
+
state_id: int,
|
112
|
+
log_file_path: Path,
|
113
|
+
logger_name: str,
|
114
|
+
exception: Exception,
|
115
|
+
db: DBSyncSession,
|
116
|
+
venv_path: Optional[Path] = None,
|
117
|
+
):
|
118
|
+
"""
|
119
|
+
Note: `venv_path` is only required to trigger the folder deletion.
|
165
120
|
"""
|
166
121
|
|
167
|
-
|
168
|
-
|
169
|
-
task_pkg.package = _normalize_package_name(task_pkg.package)
|
122
|
+
logger = get_logger(logger_name)
|
123
|
+
logger.error(f"Task collection failed. Original error: {str(exception)}")
|
170
124
|
|
171
|
-
|
172
|
-
|
173
|
-
|
125
|
+
_set_collection_state_data_status(
|
126
|
+
state_id=state_id,
|
127
|
+
new_status=CollectionStatusV2.FAIL,
|
174
128
|
logger_name=logger_name,
|
129
|
+
db=db,
|
175
130
|
)
|
176
|
-
|
177
|
-
|
131
|
+
|
132
|
+
new_log = log_file_path.open().read()
|
133
|
+
_set_collection_state_data_log(
|
134
|
+
state_id=state_id,
|
135
|
+
new_log=new_log,
|
136
|
+
logger_name=logger_name,
|
137
|
+
db=db,
|
178
138
|
)
|
179
|
-
|
139
|
+
# For backwards-compatibility, we also set state.data["info"]
|
140
|
+
_set_collection_state_data_info(
|
141
|
+
state_id=state_id,
|
142
|
+
new_info=f"Original error: {exception}",
|
143
|
+
logger_name=logger_name,
|
144
|
+
db=db,
|
145
|
+
)
|
146
|
+
# Delete corrupted package dir
|
147
|
+
if venv_path is not None:
|
148
|
+
logger.info(f"Now delete temporary folder {venv_path}")
|
149
|
+
shell_rmtree(venv_path)
|
150
|
+
logger.info("Temporary folder deleted")
|
151
|
+
|
152
|
+
reset_logger_handlers(logger)
|
153
|
+
return
|
180
154
|
|
181
155
|
|
182
|
-
|
156
|
+
def _prepare_tasks_metadata(
|
183
157
|
*,
|
184
|
-
|
185
|
-
|
186
|
-
|
158
|
+
package_manifest: ManifestV2,
|
159
|
+
package_source: str,
|
160
|
+
python_bin: Path,
|
161
|
+
package_root: Path,
|
162
|
+
package_version: Optional[str] = None,
|
187
163
|
) -> list[TaskCreateV2]:
|
188
164
|
"""
|
189
|
-
|
190
|
-
"""
|
191
|
-
|
192
|
-
logger = get_logger(logger_name)
|
165
|
+
Based on the package manifest and additional info, prepare the task list.
|
193
166
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
# Prepare task_list with appropriate metadata
|
212
|
-
logger.debug("Creating task list from manifest")
|
213
|
-
task_list = []
|
214
|
-
for t in task_pkg.package_manifest.task_list:
|
215
|
-
# Fill in attributes for TaskCreate
|
216
|
-
task_attributes = {}
|
217
|
-
task_attributes["version"] = task_pkg.package_version
|
218
|
-
task_name_slug = slugify_task_name(t.name)
|
167
|
+
Args:
|
168
|
+
package_manifest:
|
169
|
+
package_source:
|
170
|
+
python_bin:
|
171
|
+
package_root:
|
172
|
+
package_version:
|
173
|
+
"""
|
174
|
+
task_list = []
|
175
|
+
for _task in package_manifest.task_list:
|
176
|
+
# Set non-command attributes
|
177
|
+
task_attributes = {}
|
178
|
+
if package_version is not None:
|
179
|
+
task_attributes["version"] = package_version
|
180
|
+
task_name_slug = slugify_task_name(_task.name)
|
181
|
+
task_attributes["source"] = f"{package_source}:{task_name_slug}"
|
182
|
+
if package_manifest.has_args_schemas:
|
219
183
|
task_attributes[
|
220
|
-
"
|
221
|
-
] =
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
f"Cannot find executable `{non_parallel_path}` "
|
228
|
-
f"for task `{t.name}`"
|
229
|
-
)
|
230
|
-
task_attributes[
|
231
|
-
"command_non_parallel"
|
232
|
-
] = f"{python_bin.as_posix()} {non_parallel_path.as_posix()}"
|
233
|
-
if t.executable_parallel is not None:
|
234
|
-
parallel_path = package_root / t.executable_parallel
|
235
|
-
if not parallel_path.exists():
|
236
|
-
raise FileNotFoundError(
|
237
|
-
f"Cannot find executable `{parallel_path}` "
|
238
|
-
f"for task `{t.name}`"
|
239
|
-
)
|
240
|
-
task_attributes[
|
241
|
-
"command_parallel"
|
242
|
-
] = f"{python_bin.as_posix()} {parallel_path.as_posix()}"
|
243
|
-
|
244
|
-
manifest = task_pkg.package_manifest
|
245
|
-
if manifest.has_args_schemas:
|
246
|
-
task_attributes[
|
247
|
-
"args_schema_version"
|
248
|
-
] = manifest.args_schema_version
|
249
|
-
|
250
|
-
this_task = TaskCreateV2(
|
251
|
-
**t.dict(
|
252
|
-
exclude={"executable_non_parallel", "executable_parallel"}
|
253
|
-
),
|
254
|
-
**task_attributes,
|
184
|
+
"args_schema_version"
|
185
|
+
] = package_manifest.args_schema_version
|
186
|
+
# Set command attributes
|
187
|
+
if _task.executable_non_parallel is not None:
|
188
|
+
non_parallel_path = package_root / _task.executable_non_parallel
|
189
|
+
task_attributes["command_non_parallel"] = (
|
190
|
+
f"{python_bin.as_posix()} " f"{non_parallel_path.as_posix()}"
|
255
191
|
)
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
192
|
+
if _task.executable_parallel is not None:
|
193
|
+
parallel_path = package_root / _task.executable_parallel
|
194
|
+
task_attributes[
|
195
|
+
"command_parallel"
|
196
|
+
] = f"{python_bin.as_posix()} {parallel_path.as_posix()}"
|
197
|
+
# Create object
|
198
|
+
task_obj = TaskCreateV2(
|
199
|
+
**_task.dict(
|
200
|
+
exclude={
|
201
|
+
"executable_non_parallel",
|
202
|
+
"executable_parallel",
|
203
|
+
}
|
204
|
+
),
|
205
|
+
**task_attributes,
|
206
|
+
)
|
207
|
+
task_list.append(task_obj)
|
261
208
|
return task_list
|
262
209
|
|
263
210
|
|
264
|
-
def
|
265
|
-
if task.command_non_parallel is None:
|
266
|
-
return "parallel"
|
267
|
-
elif task.command_parallel is None:
|
268
|
-
return "non_parallel"
|
269
|
-
else:
|
270
|
-
return "compound"
|
271
|
-
|
272
|
-
|
273
|
-
async def _insert_tasks(
|
274
|
-
task_list: list[TaskCreateV2],
|
275
|
-
db: DBSyncSession,
|
276
|
-
) -> list[TaskV2]:
|
277
|
-
"""
|
278
|
-
Insert tasks into database
|
211
|
+
def _check_task_files_exist(task_list: list[TaskCreateV2]) -> None:
|
279
212
|
"""
|
213
|
+
Check that the modules listed in task commands point to existing files.
|
280
214
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
215
|
+
Args: task_list
|
216
|
+
"""
|
217
|
+
for _task in task_list:
|
218
|
+
if _task.command_non_parallel is not None:
|
219
|
+
_task_path = _task.command_non_parallel.split()[1]
|
220
|
+
if not Path(_task_path).exists():
|
221
|
+
raise FileNotFoundError(
|
222
|
+
f"Task `{_task.name}` has `command_non_parallel` "
|
223
|
+
f"pointing to missing file `{_task_path}`."
|
224
|
+
)
|
225
|
+
if _task.command_parallel is not None:
|
226
|
+
_task_path = _task.command_parallel.split()[1]
|
227
|
+
if not Path(_task_path).exists():
|
228
|
+
raise FileNotFoundError(
|
229
|
+
f"Task `{_task.name}` has `command_parallel` "
|
230
|
+
f"pointing to missing file `{_task_path}`."
|
231
|
+
)
|
290
232
|
|
291
233
|
|
292
234
|
async def background_collect_pip(
|
@@ -295,89 +237,109 @@ async def background_collect_pip(
|
|
295
237
|
task_pkg: _TaskCollectPip,
|
296
238
|
) -> None:
|
297
239
|
"""
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
240
|
+
Setup venv, install package, collect tasks.
|
241
|
+
|
242
|
+
This function (executed as background task), includes the several steps
|
243
|
+
associated to automated collection of a Python task package.
|
244
|
+
1. Preliminary checks
|
245
|
+
2. Create venv and run `pip install`
|
246
|
+
3. Collect tasks into db
|
247
|
+
4. Finalize things.
|
248
|
+
5. Handle failures by copying the log into the state and deleting the
|
249
|
+
package directory.
|
305
250
|
"""
|
306
251
|
logger_name = task_pkg.package.replace("/", "_")
|
307
252
|
logger = set_logger(
|
308
253
|
logger_name=logger_name,
|
309
254
|
log_file_path=get_log_path(venv_path),
|
310
255
|
)
|
311
|
-
|
256
|
+
|
257
|
+
# Start
|
258
|
+
logger.debug("START")
|
312
259
|
for key, value in task_pkg.dict(exclude={"package_manifest"}).items():
|
313
|
-
logger.debug(f"{key}: {value}")
|
260
|
+
logger.debug(f"task_pkg.{key}: {value}")
|
314
261
|
|
315
262
|
with next(get_sync_db()) as db:
|
316
|
-
state: CollectionStateV2 = db.get(CollectionStateV2, state_id)
|
317
|
-
data = TaskCollectStatusV2(**state.data)
|
318
|
-
data.info = None
|
319
263
|
|
320
264
|
try:
|
321
|
-
#
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
265
|
+
# Block 1: preliminary checks (only proceed if version and
|
266
|
+
# manifest attributes are set).
|
267
|
+
# Required: task_pkg
|
268
|
+
task_pkg.check()
|
269
|
+
|
270
|
+
# Block 2: create venv and run pip install
|
271
|
+
# Required: state_id, venv_path, task_pkg
|
272
|
+
logger.debug("installing - START")
|
273
|
+
_set_collection_state_data_status(
|
274
|
+
state_id=state_id,
|
275
|
+
new_status=CollectionStatusV2.INSTALLING,
|
276
|
+
logger_name=logger_name,
|
277
|
+
db=db,
|
278
|
+
)
|
279
|
+
python_bin, package_root = await _create_venv_install_package_pip(
|
280
|
+
path=venv_path,
|
330
281
|
task_pkg=task_pkg,
|
331
282
|
logger_name=logger_name,
|
332
283
|
)
|
284
|
+
logger.debug("installing - END")
|
285
|
+
|
286
|
+
# Block 3: create task metadata and create database entries
|
287
|
+
# Required: state_id, python_bin, package_root, task_pkg
|
288
|
+
logger.debug("collecting - START")
|
289
|
+
_set_collection_state_data_status(
|
290
|
+
state_id=state_id,
|
291
|
+
new_status=CollectionStatusV2.COLLECTING,
|
292
|
+
logger_name=logger_name,
|
293
|
+
db=db,
|
294
|
+
)
|
295
|
+
logger.debug("collecting - prepare tasks and update db " "- START")
|
296
|
+
task_list = _prepare_tasks_metadata(
|
297
|
+
package_manifest=task_pkg.package_manifest,
|
298
|
+
package_version=task_pkg.package_version,
|
299
|
+
package_source=task_pkg.package_source,
|
300
|
+
package_root=package_root,
|
301
|
+
python_bin=python_bin,
|
302
|
+
)
|
303
|
+
_check_task_files_exist(task_list=task_list)
|
304
|
+
tasks = _insert_tasks(task_list=task_list, db=db)
|
305
|
+
logger.debug("collecting - prepare tasks and update db " "- END")
|
306
|
+
logger.debug("collecting - END")
|
333
307
|
|
334
|
-
#
|
335
|
-
logger.debug("
|
336
|
-
data.status = "collecting"
|
337
|
-
state.data = data.sanitised_dict()
|
338
|
-
db.merge(state)
|
339
|
-
db.commit()
|
340
|
-
tasks = await _insert_tasks(task_list=task_list, db=db)
|
341
|
-
|
342
|
-
# finalise
|
343
|
-
logger.debug("Task-collection status: finalising")
|
308
|
+
# Block 4: finalize (write collection files, write metadata to DB)
|
309
|
+
logger.debug("finalising - START")
|
344
310
|
collection_path = get_collection_path(venv_path)
|
345
|
-
|
346
|
-
|
311
|
+
collection_state = db.get(CollectionStateV2, state_id)
|
312
|
+
task_read_list = [
|
313
|
+
TaskReadV2(**task.model_dump()).dict() for task in tasks
|
347
314
|
]
|
315
|
+
collection_state.data["task_list"] = task_read_list
|
316
|
+
collection_state.data["log"] = get_collection_log(venv_path)
|
317
|
+
collection_state.data["freeze"] = get_collection_freeze(venv_path)
|
348
318
|
with collection_path.open("w") as f:
|
349
|
-
json.dump(data
|
319
|
+
json.dump(collection_state.data, f, indent=2)
|
350
320
|
|
351
|
-
|
352
|
-
data.status = "OK"
|
353
|
-
data.log = get_collection_log(venv_path)
|
354
|
-
state.data = data.sanitised_dict()
|
355
|
-
db.merge(state)
|
321
|
+
flag_modified(collection_state, "data")
|
356
322
|
db.commit()
|
357
|
-
|
358
|
-
# Write last logs to file
|
359
|
-
logger.debug("Task-collection status: OK")
|
360
|
-
logger.info("Background task collection completed successfully")
|
361
|
-
reset_logger_handlers(logger)
|
362
|
-
|
363
|
-
db.close()
|
323
|
+
logger.debug("finalising - END")
|
364
324
|
|
365
325
|
except Exception as e:
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
db.commit()
|
377
|
-
db.close()
|
326
|
+
logfile_path = get_log_path(get_absolute_venv_path(venv_path))
|
327
|
+
_handle_failure(
|
328
|
+
state_id=state_id,
|
329
|
+
log_file_path=logfile_path,
|
330
|
+
logger_name=logger_name,
|
331
|
+
exception=e,
|
332
|
+
db=db,
|
333
|
+
venv_path=venv_path,
|
334
|
+
)
|
335
|
+
return
|
378
336
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
337
|
+
logger.debug("Task-collection status: OK")
|
338
|
+
logger.info("Background task collection completed successfully")
|
339
|
+
_set_collection_state_data_status(
|
340
|
+
state_id=state_id,
|
341
|
+
new_status=CollectionStatusV2.OK,
|
342
|
+
logger_name=logger_name,
|
343
|
+
db=db,
|
344
|
+
)
|
345
|
+
reset_logger_handlers(logger)
|