fractal-server 2.7.0a2__py3-none-any.whl → 2.7.0a4__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/__main__.py +3 -9
- fractal_server/app/models/v2/collection_state.py +1 -0
- fractal_server/app/models/v2/task.py +27 -3
- fractal_server/app/routes/admin/v2/task.py +5 -13
- fractal_server/app/routes/admin/v2/task_group.py +21 -0
- fractal_server/app/routes/api/v1/task_collection.py +2 -2
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +75 -2
- fractal_server/app/routes/api/v2/task.py +16 -42
- fractal_server/app/routes/api/v2/task_collection.py +148 -187
- fractal_server/app/routes/api/v2/task_collection_custom.py +31 -58
- fractal_server/app/routes/api/v2/task_group.py +25 -1
- fractal_server/app/routes/api/v2/workflow.py +18 -34
- fractal_server/app/routes/auth/_aux_auth.py +15 -12
- fractal_server/app/routes/auth/group.py +46 -23
- fractal_server/app/runner/v2/task_interface.py +4 -9
- fractal_server/app/schemas/v2/dataset.py +2 -7
- fractal_server/app/schemas/v2/dumps.py +1 -1
- fractal_server/app/schemas/v2/job.py +1 -1
- fractal_server/app/schemas/v2/project.py +1 -1
- fractal_server/app/schemas/v2/task.py +5 -5
- fractal_server/app/schemas/v2/task_collection.py +8 -6
- fractal_server/app/schemas/v2/task_group.py +31 -3
- fractal_server/app/schemas/v2/workflow.py +2 -2
- fractal_server/app/schemas/v2/workflowtask.py +2 -2
- fractal_server/data_migrations/2_7_0.py +1 -11
- fractal_server/images/models.py +2 -4
- fractal_server/main.py +1 -1
- fractal_server/migrations/versions/034a469ec2eb_task_groups.py +184 -0
- fractal_server/string_tools.py +6 -2
- fractal_server/tasks/v1/_TaskCollectPip.py +1 -1
- fractal_server/tasks/v1/background_operations.py +2 -2
- fractal_server/tasks/v2/_venv_pip.py +62 -70
- fractal_server/tasks/v2/background_operations.py +168 -49
- fractal_server/tasks/v2/background_operations_ssh.py +35 -77
- fractal_server/tasks/v2/database_operations.py +7 -17
- fractal_server/tasks/v2/endpoint_operations.py +0 -134
- fractal_server/tasks/v2/templates/_1_create_venv.sh +9 -5
- fractal_server/tasks/v2/utils.py +5 -0
- fractal_server/utils.py +3 -2
- {fractal_server-2.7.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/METADATA +1 -1
- {fractal_server-2.7.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/RECORD +45 -48
- fractal_server/migrations/versions/742b74e1cc6e_revamp_taskv2_and_taskgroupv2.py +0 -101
- fractal_server/migrations/versions/7cf1baae8fb4_task_group_v2.py +0 -66
- fractal_server/migrations/versions/df7cc3501bf7_linkusergroup_timestamp_created.py +0 -42
- fractal_server/tasks/v2/_TaskCollectPip.py +0 -132
- {fractal_server-2.7.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/LICENSE +0 -0
- {fractal_server-2.7.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/WHEEL +0 -0
- {fractal_server-2.7.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/entry_points.txt +0 -0
@@ -5,30 +5,33 @@ 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 tempfile import TemporaryDirectory
|
8
9
|
from typing import Optional
|
10
|
+
from typing import Union
|
11
|
+
from zipfile import ZipFile
|
9
12
|
|
10
13
|
from sqlalchemy.orm import Session as DBSyncSession
|
11
14
|
from sqlalchemy.orm.attributes import flag_modified
|
15
|
+
from sqlmodel import select
|
12
16
|
|
13
|
-
from ...string_tools import slugify_task_name_for_source
|
14
|
-
from ..utils import get_absolute_venv_path
|
15
17
|
from ..utils import get_collection_freeze
|
16
18
|
from ..utils import get_collection_log
|
17
19
|
from ..utils import get_collection_path
|
18
20
|
from ..utils import get_log_path
|
19
|
-
from .
|
20
|
-
from .database_operations import create_db_task_group_and_tasks
|
21
|
+
from .database_operations import create_db_tasks_and_update_task_group
|
21
22
|
from fractal_server.app.db import get_sync_db
|
22
23
|
from fractal_server.app.models.v2 import CollectionStateV2
|
24
|
+
from fractal_server.app.models.v2 import TaskGroupV2
|
23
25
|
from fractal_server.app.schemas.v2 import CollectionStatusV2
|
24
26
|
from fractal_server.app.schemas.v2 import TaskCreateV2
|
25
|
-
from fractal_server.app.schemas.v2 import TaskGroupCreateV2
|
26
27
|
from fractal_server.app.schemas.v2 import TaskReadV2
|
27
28
|
from fractal_server.app.schemas.v2.manifest import ManifestV2
|
28
29
|
from fractal_server.logger import get_logger
|
29
30
|
from fractal_server.logger import reset_logger_handlers
|
30
31
|
from fractal_server.logger import set_logger
|
31
32
|
from fractal_server.tasks.v2._venv_pip import _create_venv_install_package_pip
|
33
|
+
from fractal_server.tasks.v2.utils import get_python_interpreter_v2
|
34
|
+
from fractal_server.utils import execute_command
|
32
35
|
|
33
36
|
|
34
37
|
def _set_collection_state_data_status(
|
@@ -82,7 +85,8 @@ def _handle_failure(
|
|
82
85
|
logger_name: str,
|
83
86
|
exception: Exception,
|
84
87
|
db: DBSyncSession,
|
85
|
-
|
88
|
+
task_group_id: int,
|
89
|
+
path: Optional[Path] = None,
|
86
90
|
):
|
87
91
|
"""
|
88
92
|
Note: `venv_path` is only required to trigger the folder deletion.
|
@@ -113,11 +117,32 @@ def _handle_failure(
|
|
113
117
|
db=db,
|
114
118
|
)
|
115
119
|
# Delete corrupted package dir
|
116
|
-
if
|
117
|
-
logger.info(f"Now delete temporary folder {
|
118
|
-
shell_rmtree(
|
120
|
+
if path is not None and Path(path).exists():
|
121
|
+
logger.info(f"Now delete temporary folder {path}")
|
122
|
+
shell_rmtree(path)
|
119
123
|
logger.info("Temporary folder deleted")
|
120
124
|
|
125
|
+
# Delete TaskGroupV2 object / and apply cascade operation to FKs
|
126
|
+
logger.info(f"Now delete TaskGroupV2 with {task_group_id=}")
|
127
|
+
logger.info("Start of CollectionStateV2 cascade operations.")
|
128
|
+
stm = select(CollectionStateV2).where(
|
129
|
+
CollectionStateV2.taskgroupv2_id == task_group_id
|
130
|
+
)
|
131
|
+
res = db.execute(stm)
|
132
|
+
collection_states = res.scalars().all()
|
133
|
+
for collection_state in collection_states:
|
134
|
+
logger.info(
|
135
|
+
f"Setting CollectionStateV2[{collection_state.id}].taskgroupv2_id "
|
136
|
+
"to None."
|
137
|
+
)
|
138
|
+
collection_state.taskgroupv2_id = None
|
139
|
+
db.add(collection_state)
|
140
|
+
logger.info("End of CollectionStateV2 cascade operations.")
|
141
|
+
task_group = db.get(TaskGroupV2, task_group_id)
|
142
|
+
db.delete(task_group)
|
143
|
+
db.commit()
|
144
|
+
logger.info(f"TaskGroupV2 with {task_group_id=} deleted")
|
145
|
+
|
121
146
|
reset_logger_handlers(logger)
|
122
147
|
return
|
123
148
|
|
@@ -125,7 +150,6 @@ def _handle_failure(
|
|
125
150
|
def _prepare_tasks_metadata(
|
126
151
|
*,
|
127
152
|
package_manifest: ManifestV2,
|
128
|
-
package_source: str,
|
129
153
|
python_bin: Path,
|
130
154
|
package_root: Path,
|
131
155
|
package_version: Optional[str] = None,
|
@@ -135,7 +159,6 @@ def _prepare_tasks_metadata(
|
|
135
159
|
|
136
160
|
Args:
|
137
161
|
package_manifest:
|
138
|
-
package_source:
|
139
162
|
python_bin:
|
140
163
|
package_root:
|
141
164
|
package_version:
|
@@ -146,8 +169,6 @@ def _prepare_tasks_metadata(
|
|
146
169
|
task_attributes = {}
|
147
170
|
if package_version is not None:
|
148
171
|
task_attributes["version"] = package_version
|
149
|
-
task_name_slug = slugify_task_name_for_source(_task.name)
|
150
|
-
task_attributes["source"] = f"{package_source}:{task_name_slug}"
|
151
172
|
if package_manifest.has_args_schemas:
|
152
173
|
task_attributes[
|
153
174
|
"args_schema_version"
|
@@ -201,13 +222,95 @@ def _check_task_files_exist(task_list: list[TaskCreateV2]) -> None:
|
|
201
222
|
)
|
202
223
|
|
203
224
|
|
225
|
+
async def _download_package(
|
226
|
+
*,
|
227
|
+
python_version: str,
|
228
|
+
pkg_name: str,
|
229
|
+
version: str,
|
230
|
+
dest: Union[str, Path],
|
231
|
+
) -> Path:
|
232
|
+
"""
|
233
|
+
Download package to destination and return wheel-file path.
|
234
|
+
"""
|
235
|
+
python_bin = get_python_interpreter_v2(python_version=python_version)
|
236
|
+
pip = f"{python_bin} -m pip"
|
237
|
+
package_and_version = f"{pkg_name}=={version}"
|
238
|
+
cmd = f"{pip} download --no-deps {package_and_version} -d {dest}"
|
239
|
+
stdout = await execute_command(command=cmd)
|
240
|
+
pkg_file = next(
|
241
|
+
line.split()[-1] for line in stdout.split("\n") if "Saved" in line
|
242
|
+
)
|
243
|
+
return Path(pkg_file)
|
244
|
+
|
245
|
+
|
246
|
+
def _load_manifest_from_wheel(
|
247
|
+
wheel_file_path: str,
|
248
|
+
logger_name: str,
|
249
|
+
) -> ManifestV2:
|
250
|
+
"""
|
251
|
+
Given a wheel file on-disk, extract the Fractal manifest.
|
252
|
+
"""
|
253
|
+
logger = get_logger(logger_name)
|
254
|
+
|
255
|
+
with ZipFile(wheel_file_path) as wheel:
|
256
|
+
namelist = wheel.namelist()
|
257
|
+
try:
|
258
|
+
manifest = next(
|
259
|
+
name
|
260
|
+
for name in namelist
|
261
|
+
if "__FRACTAL_MANIFEST__.json" in name
|
262
|
+
)
|
263
|
+
except StopIteration:
|
264
|
+
msg = (
|
265
|
+
f"{wheel_file_path} does not include __FRACTAL_MANIFEST__.json"
|
266
|
+
)
|
267
|
+
logger.error(msg)
|
268
|
+
raise ValueError(msg)
|
269
|
+
with wheel.open(manifest) as manifest_fd:
|
270
|
+
manifest_dict = json.load(manifest_fd)
|
271
|
+
manifest_version = str(manifest_dict["manifest_version"])
|
272
|
+
if manifest_version != "2":
|
273
|
+
msg = f"Manifest version {manifest_version=} not supported"
|
274
|
+
logger.error(msg)
|
275
|
+
raise ValueError(msg)
|
276
|
+
pkg_manifest = ManifestV2(**manifest_dict)
|
277
|
+
return pkg_manifest
|
278
|
+
|
279
|
+
|
280
|
+
async def _get_package_manifest(
|
281
|
+
*,
|
282
|
+
task_group: TaskGroupV2,
|
283
|
+
logger_name: str,
|
284
|
+
) -> ManifestV2:
|
285
|
+
wheel_file_path = task_group.wheel_path
|
286
|
+
if wheel_file_path is None:
|
287
|
+
with TemporaryDirectory() as tmpdir:
|
288
|
+
# Copy or download the package wheel file to tmpdir
|
289
|
+
wheel_file_path = await _download_package(
|
290
|
+
python_version=task_group.python_version,
|
291
|
+
pkg_name=task_group.pkg_name,
|
292
|
+
version=task_group.version,
|
293
|
+
dest=tmpdir,
|
294
|
+
)
|
295
|
+
wheel_file_path = wheel_file_path.as_posix()
|
296
|
+
# Read package manifest from temporary wheel file
|
297
|
+
manifest = _load_manifest_from_wheel(
|
298
|
+
wheel_file_path=wheel_file_path,
|
299
|
+
logger_name=logger_name,
|
300
|
+
)
|
301
|
+
else:
|
302
|
+
# Read package manifest from wheel file
|
303
|
+
manifest = _load_manifest_from_wheel(
|
304
|
+
wheel_file_path=wheel_file_path,
|
305
|
+
logger_name=logger_name,
|
306
|
+
)
|
307
|
+
return manifest
|
308
|
+
|
309
|
+
|
204
310
|
async def background_collect_pip(
|
205
311
|
*,
|
206
312
|
state_id: int,
|
207
|
-
|
208
|
-
task_pkg: _TaskCollectPip,
|
209
|
-
user_id: int,
|
210
|
-
user_group_id: Optional[int],
|
313
|
+
task_group: TaskGroupV2,
|
211
314
|
) -> None:
|
212
315
|
"""
|
213
316
|
Setup venv, install package, collect tasks.
|
@@ -221,24 +324,48 @@ async def background_collect_pip(
|
|
221
324
|
5. Handle failures by copying the log into the state and deleting the
|
222
325
|
package directory.
|
223
326
|
"""
|
224
|
-
logger_name =
|
327
|
+
logger_name = (
|
328
|
+
f"{task_group.user_id}-{task_group.pkg_name}-{task_group.version}"
|
329
|
+
)
|
330
|
+
|
331
|
+
try:
|
332
|
+
Path(task_group.path).mkdir(parents=True, exist_ok=False)
|
333
|
+
except FileExistsError as e:
|
334
|
+
logger = set_logger(
|
335
|
+
logger_name=logger_name,
|
336
|
+
log_file_path=get_log_path(Path(task_group.path)),
|
337
|
+
)
|
338
|
+
|
339
|
+
logfile_path = get_log_path(Path(task_group.path))
|
340
|
+
with next(get_sync_db()) as db:
|
341
|
+
_handle_failure(
|
342
|
+
state_id=state_id,
|
343
|
+
log_file_path=logfile_path,
|
344
|
+
logger_name=logger_name,
|
345
|
+
exception=e,
|
346
|
+
db=db,
|
347
|
+
path=None, # Do not remove an existing path
|
348
|
+
task_group_id=task_group.id,
|
349
|
+
)
|
350
|
+
return
|
351
|
+
|
225
352
|
logger = set_logger(
|
226
353
|
logger_name=logger_name,
|
227
|
-
log_file_path=get_log_path(
|
354
|
+
log_file_path=get_log_path(Path(task_group.path)),
|
228
355
|
)
|
229
356
|
|
230
357
|
# Start
|
231
358
|
logger.debug("START")
|
232
|
-
for key, value in
|
233
|
-
logger.debug(f"
|
359
|
+
for key, value in task_group.model_dump().items():
|
360
|
+
logger.debug(f"task_group.{key}: {value}")
|
234
361
|
|
235
362
|
with next(get_sync_db()) as db:
|
236
|
-
|
237
363
|
try:
|
238
|
-
# Block 1:
|
239
|
-
|
240
|
-
|
241
|
-
|
364
|
+
# Block 1: get and validate manfifest
|
365
|
+
pkg_manifest = await _get_package_manifest(
|
366
|
+
task_group=task_group,
|
367
|
+
logger_name=logger_name,
|
368
|
+
)
|
242
369
|
|
243
370
|
# Block 2: create venv and run pip install
|
244
371
|
# Required: state_id, venv_path, task_pkg
|
@@ -250,8 +377,7 @@ async def background_collect_pip(
|
|
250
377
|
db=db,
|
251
378
|
)
|
252
379
|
python_bin, package_root = await _create_venv_install_package_pip(
|
253
|
-
|
254
|
-
task_pkg=task_pkg,
|
380
|
+
task_group=task_group,
|
255
381
|
logger_name=logger_name,
|
256
382
|
)
|
257
383
|
logger.debug("installing - END")
|
@@ -267,29 +393,17 @@ async def background_collect_pip(
|
|
267
393
|
)
|
268
394
|
logger.debug("collecting - prepare tasks and update db " "- START")
|
269
395
|
task_list = _prepare_tasks_metadata(
|
270
|
-
package_manifest=
|
271
|
-
package_version=
|
272
|
-
package_source=task_pkg.package_source,
|
396
|
+
package_manifest=pkg_manifest,
|
397
|
+
package_version=task_group.version,
|
273
398
|
package_root=package_root,
|
274
399
|
python_bin=python_bin,
|
275
400
|
)
|
276
401
|
_check_task_files_exist(task_list=task_list)
|
277
402
|
|
278
403
|
# Prepare some task-group attributes
|
279
|
-
|
280
|
-
pkg_name=task_pkg.package_name,
|
281
|
-
version=task_pkg.package_version,
|
282
|
-
)
|
283
|
-
if task_pkg.is_local_package:
|
284
|
-
task_group_attrs["origin"] = "wheel-file"
|
285
|
-
else:
|
286
|
-
task_group_attrs["origin"] = "pypi"
|
287
|
-
|
288
|
-
task_group = create_db_task_group_and_tasks(
|
404
|
+
task_group = create_db_tasks_and_update_task_group(
|
289
405
|
task_list=task_list,
|
290
|
-
|
291
|
-
user_id=user_id,
|
292
|
-
user_group_id=user_group_id,
|
406
|
+
task_group_id=task_group.id,
|
293
407
|
db=db,
|
294
408
|
)
|
295
409
|
|
@@ -298,15 +412,19 @@ async def background_collect_pip(
|
|
298
412
|
|
299
413
|
# Block 4: finalize (write collection files, write metadata to DB)
|
300
414
|
logger.debug("finalising - START")
|
301
|
-
collection_path = get_collection_path(
|
415
|
+
collection_path = get_collection_path(Path(task_group.path))
|
302
416
|
collection_state = db.get(CollectionStateV2, state_id)
|
303
417
|
task_read_list = [
|
304
418
|
TaskReadV2(**task.model_dump()).dict()
|
305
419
|
for task in task_group.task_list
|
306
420
|
]
|
307
421
|
collection_state.data["task_list"] = task_read_list
|
308
|
-
collection_state.data["log"] = get_collection_log(
|
309
|
-
|
422
|
+
collection_state.data["log"] = get_collection_log(
|
423
|
+
Path(task_group.path)
|
424
|
+
)
|
425
|
+
collection_state.data["freeze"] = get_collection_freeze(
|
426
|
+
Path(task_group.path)
|
427
|
+
)
|
310
428
|
with collection_path.open("w") as f:
|
311
429
|
json.dump(collection_state.data, f, indent=2)
|
312
430
|
|
@@ -315,14 +433,15 @@ async def background_collect_pip(
|
|
315
433
|
logger.debug("finalising - END")
|
316
434
|
|
317
435
|
except Exception as e:
|
318
|
-
logfile_path = get_log_path(
|
436
|
+
logfile_path = get_log_path(Path(task_group.path))
|
319
437
|
_handle_failure(
|
320
438
|
state_id=state_id,
|
321
439
|
log_file_path=logfile_path,
|
322
440
|
logger_name=logger_name,
|
323
441
|
exception=e,
|
324
442
|
db=db,
|
325
|
-
|
443
|
+
path=task_group.path,
|
444
|
+
task_group_id=task_group.id,
|
326
445
|
)
|
327
446
|
return
|
328
447
|
|
@@ -2,19 +2,17 @@ import json
|
|
2
2
|
import os
|
3
3
|
from pathlib import Path
|
4
4
|
from tempfile import TemporaryDirectory
|
5
|
-
from typing import Optional
|
6
5
|
|
7
6
|
from sqlalchemy.orm.attributes import flag_modified
|
8
7
|
|
9
|
-
from ...app.models.v2 import CollectionStateV2
|
10
|
-
from ._TaskCollectPip import _TaskCollectPip
|
11
8
|
from .background_operations import _handle_failure
|
12
9
|
from .background_operations import _prepare_tasks_metadata
|
13
10
|
from .background_operations import _set_collection_state_data_status
|
14
|
-
from .database_operations import
|
11
|
+
from .database_operations import create_db_tasks_and_update_task_group
|
15
12
|
from fractal_server.app.db import get_sync_db
|
13
|
+
from fractal_server.app.models.v2 import CollectionStateV2
|
14
|
+
from fractal_server.app.models.v2 import TaskGroupV2
|
16
15
|
from fractal_server.app.schemas.v2 import CollectionStatusV2
|
17
|
-
from fractal_server.app.schemas.v2 import TaskGroupCreateV2
|
18
16
|
from fractal_server.app.schemas.v2.manifest import ManifestV2
|
19
17
|
from fractal_server.config import get_settings
|
20
18
|
from fractal_server.logger import get_logger
|
@@ -112,11 +110,9 @@ def _customize_and_run_template(
|
|
112
110
|
def background_collect_pip_ssh(
|
113
111
|
*,
|
114
112
|
state_id: int,
|
115
|
-
|
113
|
+
task_group: TaskGroupV2,
|
116
114
|
fractal_ssh: FractalSSH,
|
117
115
|
tasks_base_dir: str,
|
118
|
-
user_id: int,
|
119
|
-
user_group_id: Optional[int],
|
120
116
|
) -> None:
|
121
117
|
"""
|
122
118
|
Collect a task package over SSH
|
@@ -131,7 +127,7 @@ def background_collect_pip_ssh(
|
|
131
127
|
|
132
128
|
Arguments:
|
133
129
|
state_id:
|
134
|
-
|
130
|
+
task_group:
|
135
131
|
fractal_ssh:
|
136
132
|
tasks_base_dir:
|
137
133
|
"""
|
@@ -144,47 +140,23 @@ def background_collect_pip_ssh(
|
|
144
140
|
logger_name=LOGGER_NAME,
|
145
141
|
log_file_path=log_file_path,
|
146
142
|
)
|
147
|
-
|
148
143
|
logger.debug("START")
|
149
|
-
for key, value in
|
150
|
-
logger.debug(f"
|
144
|
+
for key, value in task_group.model_dump().items():
|
145
|
+
logger.debug(f"task_group.{key}: {value}")
|
151
146
|
|
152
147
|
# Open a DB session soon, since it is needed for updating `state`
|
153
148
|
with next(get_sync_db()) as db:
|
154
149
|
try:
|
155
150
|
# Prepare replacements for task-collection scripts
|
156
151
|
python_bin = get_python_interpreter_v2(
|
157
|
-
python_version=
|
158
|
-
)
|
159
|
-
package_version = (
|
160
|
-
""
|
161
|
-
if task_pkg.package_version is None
|
162
|
-
else task_pkg.package_version
|
152
|
+
python_version=task_group.python_version
|
163
153
|
)
|
164
|
-
|
165
|
-
install_string = task_pkg.package
|
166
|
-
if task_pkg.package_extras is not None:
|
167
|
-
install_string = (
|
168
|
-
f"{install_string}[{task_pkg.package_extras}]"
|
169
|
-
)
|
170
|
-
if (
|
171
|
-
task_pkg.package_version is not None
|
172
|
-
and not task_pkg.is_local_package
|
173
|
-
):
|
174
|
-
install_string = (
|
175
|
-
f"{install_string}=={task_pkg.package_version}"
|
176
|
-
)
|
177
|
-
package_env_dir = (
|
178
|
-
Path(tasks_base_dir)
|
179
|
-
/ ".fractal"
|
180
|
-
/ f"{task_pkg.package_name}{package_version}"
|
181
|
-
).as_posix()
|
182
|
-
logger.debug(f"{package_env_dir=}")
|
154
|
+
install_string = task_group.pip_install_string
|
183
155
|
settings = Inject(get_settings)
|
184
156
|
replacements = [
|
185
|
-
("__PACKAGE_NAME__",
|
186
|
-
("
|
187
|
-
("
|
157
|
+
("__PACKAGE_NAME__", task_group.pkg_name),
|
158
|
+
("__TASK_GROUP_DIR__", task_group.path),
|
159
|
+
("__PACKAGE_ENV_DIR__", task_group.venv_path),
|
188
160
|
("__PYTHON__", python_bin),
|
189
161
|
("__INSTALL_STRING__", install_string),
|
190
162
|
(
|
@@ -263,13 +235,13 @@ def background_collect_pip_ssh(
|
|
263
235
|
f"collecting - parsed from pip-show: {key}={value}"
|
264
236
|
)
|
265
237
|
# Check package_name match
|
266
|
-
# FIXME SSH: Does this work for non-canonical
|
238
|
+
# FIXME SSH: Does this work well for non-canonical names?
|
267
239
|
package_name_pip_show = pkg_attrs.get("package_name")
|
268
|
-
|
269
|
-
if package_name_pip_show !=
|
240
|
+
package_name_task_group = task_group.pkg_name
|
241
|
+
if package_name_pip_show != package_name_task_group:
|
270
242
|
error_msg = (
|
271
243
|
f"`package_name` mismatch: "
|
272
|
-
f"{
|
244
|
+
f"{package_name_task_group=} but "
|
273
245
|
f"{package_name_pip_show=}"
|
274
246
|
)
|
275
247
|
logger.error(error_msg)
|
@@ -292,47 +264,32 @@ def background_collect_pip_ssh(
|
|
292
264
|
|
293
265
|
# Read and validate remote manifest file
|
294
266
|
with fractal_ssh.sftp().open(manifest_path_remote, "r") as f:
|
295
|
-
|
267
|
+
pkg_manifest_dict = json.load(f)
|
296
268
|
logger.info(f"collecting - loaded {manifest_path_remote=}")
|
297
|
-
ManifestV2(**
|
269
|
+
pkg_manifest = ManifestV2(**pkg_manifest_dict)
|
298
270
|
logger.info("collecting - manifest is a valid ManifestV2")
|
299
271
|
|
300
|
-
|
301
|
-
new_pkg = _TaskCollectPip(
|
302
|
-
**task_pkg.dict(
|
303
|
-
exclude={"package_version", "package_name"},
|
304
|
-
exclude_unset=True,
|
305
|
-
exclude_none=True,
|
306
|
-
),
|
307
|
-
package_manifest=manifest,
|
308
|
-
**pkg_attrs,
|
309
|
-
)
|
310
|
-
|
272
|
+
logger.info("collecting - _prepare_tasks_metadata - start")
|
311
273
|
task_list = _prepare_tasks_metadata(
|
312
|
-
package_manifest=
|
313
|
-
package_version=
|
314
|
-
package_source=new_pkg.package_source,
|
274
|
+
package_manifest=pkg_manifest,
|
275
|
+
package_version=task_group.version,
|
315
276
|
package_root=Path(package_root_remote),
|
316
277
|
python_bin=Path(python_bin),
|
317
278
|
)
|
279
|
+
logger.info("collecting - _prepare_tasks_metadata - end")
|
318
280
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
version=task_pkg.package_version,
|
281
|
+
logger.info(
|
282
|
+
"collecting - create_db_tasks_and_update_task_group - "
|
283
|
+
"start"
|
323
284
|
)
|
324
|
-
|
325
|
-
task_group_attrs["origin"] = "wheel-file"
|
326
|
-
else:
|
327
|
-
task_group_attrs["origin"] = "pypi"
|
328
|
-
|
329
|
-
create_db_task_group_and_tasks(
|
285
|
+
create_db_tasks_and_update_task_group(
|
330
286
|
task_list=task_list,
|
331
|
-
|
332
|
-
user_id=user_id,
|
333
|
-
user_group_id=user_group_id,
|
287
|
+
task_group_id=task_group.id,
|
334
288
|
db=db,
|
335
289
|
)
|
290
|
+
logger.info(
|
291
|
+
"collecting - create_db_tasks_and_update_task_group - end"
|
292
|
+
)
|
336
293
|
|
337
294
|
logger.debug("collecting - END")
|
338
295
|
|
@@ -356,18 +313,19 @@ def background_collect_pip_ssh(
|
|
356
313
|
logger_name=LOGGER_NAME,
|
357
314
|
exception=e,
|
358
315
|
db=db,
|
316
|
+
task_group_id=task_group.id,
|
359
317
|
)
|
360
318
|
if remove_venv_folder_upon_failure:
|
361
319
|
try:
|
362
320
|
logger.info(
|
363
|
-
f"Now delete remote folder {
|
321
|
+
f"Now delete remote folder {task_group.path}"
|
364
322
|
)
|
365
323
|
fractal_ssh.remove_folder(
|
366
|
-
folder=
|
324
|
+
folder=task_group.path,
|
367
325
|
safe_root=tasks_base_dir,
|
368
326
|
)
|
369
327
|
logger.info(
|
370
|
-
f"Deleted remoted folder {
|
328
|
+
f"Deleted remoted folder {task_group.path}"
|
371
329
|
)
|
372
330
|
except Exception as e:
|
373
331
|
logger.error(
|
@@ -377,6 +335,6 @@ def background_collect_pip_ssh(
|
|
377
335
|
else:
|
378
336
|
logger.info(
|
379
337
|
"Not trying to remove remote folder "
|
380
|
-
f"{
|
338
|
+
f"{task_group.path}."
|
381
339
|
)
|
382
340
|
return
|
@@ -1,11 +1,8 @@
|
|
1
|
-
from typing import Optional
|
2
|
-
|
3
1
|
from sqlalchemy.orm import Session as DBSyncSession
|
4
2
|
|
5
3
|
from fractal_server.app.models.v2 import TaskGroupV2
|
6
4
|
from fractal_server.app.models.v2 import TaskV2
|
7
5
|
from fractal_server.app.schemas.v2 import TaskCreateV2
|
8
|
-
from fractal_server.app.schemas.v2 import TaskGroupCreateV2
|
9
6
|
|
10
7
|
|
11
8
|
def _get_task_type(task: TaskCreateV2) -> str:
|
@@ -17,22 +14,18 @@ def _get_task_type(task: TaskCreateV2) -> str:
|
|
17
14
|
return "compound"
|
18
15
|
|
19
16
|
|
20
|
-
def
|
17
|
+
def create_db_tasks_and_update_task_group(
|
21
18
|
*,
|
19
|
+
task_group_id: int,
|
22
20
|
task_list: list[TaskCreateV2],
|
23
|
-
task_group_obj: TaskGroupCreateV2,
|
24
|
-
user_id: int,
|
25
21
|
db: DBSyncSession,
|
26
|
-
user_group_id: Optional[int] = None,
|
27
22
|
) -> TaskGroupV2:
|
28
23
|
"""
|
29
24
|
Create a `TaskGroupV2` with N `TaskV2`s, and insert them into the database.
|
30
25
|
|
31
26
|
Arguments:
|
32
|
-
task_group:
|
33
|
-
task_list:
|
34
|
-
user_id:
|
35
|
-
user_group_id: Can be `None`
|
27
|
+
task_group: ID of an existing TaskGroupV2 object.
|
28
|
+
task_list: A list of TaskCreateV2 objects to be inserted into the db.
|
36
29
|
db: A synchronous database session
|
37
30
|
"""
|
38
31
|
actual_task_list = [
|
@@ -42,13 +35,10 @@ def create_db_task_group_and_tasks(
|
|
42
35
|
)
|
43
36
|
for task in task_list
|
44
37
|
]
|
45
|
-
task_group = TaskGroupV2
|
46
|
-
|
47
|
-
user_group_id=user_group_id,
|
48
|
-
task_list=actual_task_list,
|
49
|
-
**task_group_obj.dict(),
|
50
|
-
)
|
38
|
+
task_group = db.get(TaskGroupV2, task_group_id)
|
39
|
+
task_group.task_list = actual_task_list
|
51
40
|
db.add(task_group)
|
52
41
|
db.commit()
|
53
42
|
db.refresh(task_group)
|
43
|
+
|
54
44
|
return task_group
|