fractal-server 2.7.0a3__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.
Files changed (49) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +3 -9
  3. fractal_server/app/models/v2/collection_state.py +1 -0
  4. fractal_server/app/models/v2/task.py +27 -3
  5. fractal_server/app/routes/admin/v2/task.py +5 -13
  6. fractal_server/app/routes/admin/v2/task_group.py +21 -0
  7. fractal_server/app/routes/api/v1/task_collection.py +2 -2
  8. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +75 -2
  9. fractal_server/app/routes/api/v2/task.py +16 -42
  10. fractal_server/app/routes/api/v2/task_collection.py +148 -187
  11. fractal_server/app/routes/api/v2/task_collection_custom.py +31 -58
  12. fractal_server/app/routes/api/v2/task_group.py +25 -1
  13. fractal_server/app/routes/api/v2/workflow.py +11 -46
  14. fractal_server/app/routes/auth/_aux_auth.py +15 -12
  15. fractal_server/app/routes/auth/group.py +46 -23
  16. fractal_server/app/runner/v2/task_interface.py +4 -9
  17. fractal_server/app/schemas/v2/dataset.py +2 -7
  18. fractal_server/app/schemas/v2/dumps.py +1 -1
  19. fractal_server/app/schemas/v2/job.py +1 -1
  20. fractal_server/app/schemas/v2/project.py +1 -1
  21. fractal_server/app/schemas/v2/task.py +5 -5
  22. fractal_server/app/schemas/v2/task_collection.py +8 -6
  23. fractal_server/app/schemas/v2/task_group.py +31 -3
  24. fractal_server/app/schemas/v2/workflow.py +2 -2
  25. fractal_server/app/schemas/v2/workflowtask.py +2 -2
  26. fractal_server/data_migrations/2_7_0.py +1 -11
  27. fractal_server/images/models.py +2 -4
  28. fractal_server/main.py +1 -1
  29. fractal_server/migrations/versions/034a469ec2eb_task_groups.py +184 -0
  30. fractal_server/string_tools.py +6 -2
  31. fractal_server/tasks/v1/_TaskCollectPip.py +1 -1
  32. fractal_server/tasks/v1/background_operations.py +2 -2
  33. fractal_server/tasks/v2/_venv_pip.py +62 -70
  34. fractal_server/tasks/v2/background_operations.py +168 -49
  35. fractal_server/tasks/v2/background_operations_ssh.py +35 -77
  36. fractal_server/tasks/v2/database_operations.py +7 -17
  37. fractal_server/tasks/v2/endpoint_operations.py +0 -134
  38. fractal_server/tasks/v2/templates/_1_create_venv.sh +9 -5
  39. fractal_server/tasks/v2/utils.py +5 -0
  40. fractal_server/utils.py +3 -2
  41. {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a4.dist-info}/METADATA +1 -1
  42. {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a4.dist-info}/RECORD +45 -48
  43. fractal_server/migrations/versions/742b74e1cc6e_revamp_taskv2_and_taskgroupv2.py +0 -101
  44. fractal_server/migrations/versions/7cf1baae8fb4_task_group_v2.py +0 -66
  45. fractal_server/migrations/versions/df7cc3501bf7_linkusergroup_timestamp_created.py +0 -42
  46. fractal_server/tasks/v2/_TaskCollectPip.py +0 -132
  47. {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a4.dist-info}/LICENSE +0 -0
  48. {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a4.dist-info}/WHEEL +0 -0
  49. {fractal_server-2.7.0a3.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 ._TaskCollectPip import _TaskCollectPip
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
- venv_path: Optional[Path] = None,
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 venv_path is not None:
117
- logger.info(f"Now delete temporary folder {venv_path}")
118
- shell_rmtree(venv_path)
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
- venv_path: Path,
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 = task_pkg.package.replace("/", "_")
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(venv_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 task_pkg.dict(exclude={"package_manifest"}).items():
233
- logger.debug(f"task_pkg.{key}: {value}")
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: preliminary checks (only proceed if version and
239
- # manifest attributes are set).
240
- # Required: task_pkg
241
- task_pkg.check()
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
- path=venv_path,
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=task_pkg.package_manifest,
271
- package_version=task_pkg.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
- task_group_attrs = dict(
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
- task_group_obj=TaskGroupCreateV2(**task_group_attrs),
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(venv_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(venv_path)
309
- collection_state.data["freeze"] = get_collection_freeze(venv_path)
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(get_absolute_venv_path(venv_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
- venv_path=venv_path,
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 create_db_task_group_and_tasks
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
- task_pkg: _TaskCollectPip,
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
- task_pkg:
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 task_pkg.dict(exclude={"package_manifest"}).items():
150
- logger.debug(f"task_pkg.{key}: {value}")
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=task_pkg.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__", task_pkg.package_name),
186
- ("__PACKAGE_ENV_DIR__", package_env_dir),
187
- ("__PACKAGE__", task_pkg.package),
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 `package_name`?
238
+ # FIXME SSH: Does this work well for non-canonical names?
267
239
  package_name_pip_show = pkg_attrs.get("package_name")
268
- package_name_task_pkg = task_pkg.package_name
269
- if package_name_pip_show != package_name_task_pkg:
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"{package_name_task_pkg=} but "
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
- manifest = json.load(f)
267
+ pkg_manifest_dict = json.load(f)
296
268
  logger.info(f"collecting - loaded {manifest_path_remote=}")
297
- ManifestV2(**manifest)
269
+ pkg_manifest = ManifestV2(**pkg_manifest_dict)
298
270
  logger.info("collecting - manifest is a valid ManifestV2")
299
271
 
300
- # Create new _TaskCollectPip object
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=new_pkg.package_manifest,
313
- package_version=new_pkg.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
- # Prepare some task-group attributes
320
- task_group_attrs = dict(
321
- pkg_name=task_pkg.package_name,
322
- version=task_pkg.package_version,
281
+ logger.info(
282
+ "collecting - create_db_tasks_and_update_task_group - "
283
+ "start"
323
284
  )
324
- if task_pkg.is_local_package:
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
- task_group_obj=TaskGroupCreateV2(**task_group_attrs),
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 {package_env_dir}"
321
+ f"Now delete remote folder {task_group.path}"
364
322
  )
365
323
  fractal_ssh.remove_folder(
366
- folder=package_env_dir,
324
+ folder=task_group.path,
367
325
  safe_root=tasks_base_dir,
368
326
  )
369
327
  logger.info(
370
- f"Deleted remoted folder {package_env_dir}"
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"{package_env_dir}."
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 create_db_task_group_and_tasks(
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
- user_id=user_id,
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