fractal-server 2.6.3__py3-none-any.whl → 2.7.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/__main__.py +1 -1
- fractal_server/app/models/linkusergroup.py +11 -0
- fractal_server/app/models/v2/__init__.py +2 -0
- fractal_server/app/models/v2/collection_state.py +1 -0
- fractal_server/app/models/v2/task.py +67 -2
- fractal_server/app/routes/admin/v2/__init__.py +16 -0
- fractal_server/app/routes/admin/{v2.py → v2/job.py} +20 -191
- fractal_server/app/routes/admin/v2/project.py +43 -0
- fractal_server/app/routes/admin/v2/task.py +133 -0
- fractal_server/app/routes/admin/v2/task_group.py +162 -0
- fractal_server/app/routes/api/v1/task_collection.py +4 -4
- fractal_server/app/routes/api/v2/__init__.py +8 -0
- fractal_server/app/routes/api/v2/_aux_functions.py +1 -68
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +343 -0
- fractal_server/app/routes/api/v2/submit.py +16 -35
- fractal_server/app/routes/api/v2/task.py +85 -110
- fractal_server/app/routes/api/v2/task_collection.py +184 -196
- fractal_server/app/routes/api/v2/task_collection_custom.py +70 -64
- fractal_server/app/routes/api/v2/task_group.py +173 -0
- fractal_server/app/routes/api/v2/workflow.py +39 -102
- fractal_server/app/routes/api/v2/workflow_import.py +360 -0
- fractal_server/app/routes/api/v2/workflowtask.py +4 -8
- fractal_server/app/routes/auth/_aux_auth.py +86 -40
- fractal_server/app/routes/auth/current_user.py +5 -5
- fractal_server/app/routes/auth/group.py +73 -23
- fractal_server/app/routes/auth/router.py +0 -2
- fractal_server/app/routes/auth/users.py +8 -7
- fractal_server/app/runner/executors/slurm/ssh/executor.py +82 -63
- fractal_server/app/runner/v2/__init__.py +13 -7
- fractal_server/app/runner/v2/task_interface.py +4 -9
- fractal_server/app/schemas/user.py +1 -2
- fractal_server/app/schemas/v2/__init__.py +7 -0
- fractal_server/app/schemas/v2/dataset.py +2 -7
- fractal_server/app/schemas/v2/dumps.py +1 -2
- fractal_server/app/schemas/v2/job.py +1 -1
- fractal_server/app/schemas/v2/manifest.py +25 -1
- fractal_server/app/schemas/v2/project.py +1 -1
- fractal_server/app/schemas/v2/task.py +95 -36
- fractal_server/app/schemas/v2/task_collection.py +8 -6
- fractal_server/app/schemas/v2/task_group.py +85 -0
- fractal_server/app/schemas/v2/workflow.py +7 -2
- fractal_server/app/schemas/v2/workflowtask.py +9 -6
- fractal_server/app/security/__init__.py +8 -1
- fractal_server/config.py +8 -28
- fractal_server/data_migrations/2_7_0.py +323 -0
- fractal_server/images/models.py +2 -4
- fractal_server/main.py +1 -1
- fractal_server/migrations/env.py +4 -1
- fractal_server/migrations/versions/034a469ec2eb_task_groups.py +184 -0
- fractal_server/ssh/_fabric.py +186 -73
- fractal_server/string_tools.py +6 -2
- fractal_server/tasks/utils.py +19 -5
- fractal_server/tasks/v1/_TaskCollectPip.py +1 -1
- fractal_server/tasks/v1/background_operations.py +5 -5
- fractal_server/tasks/v1/get_collection_data.py +2 -2
- fractal_server/tasks/v2/_venv_pip.py +67 -70
- fractal_server/tasks/v2/background_operations.py +180 -69
- fractal_server/tasks/v2/background_operations_ssh.py +57 -70
- fractal_server/tasks/v2/database_operations.py +44 -0
- fractal_server/tasks/v2/endpoint_operations.py +104 -116
- fractal_server/tasks/v2/templates/_1_create_venv.sh +9 -5
- fractal_server/tasks/v2/templates/{_2_upgrade_pip.sh → _2_preliminary_pip_operations.sh} +1 -0
- fractal_server/tasks/v2/utils.py +5 -0
- fractal_server/utils.py +3 -2
- {fractal_server-2.6.3.dist-info → fractal_server-2.7.0.dist-info}/METADATA +3 -7
- {fractal_server-2.6.3.dist-info → fractal_server-2.7.0.dist-info}/RECORD +70 -61
- fractal_server/app/routes/auth/group_names.py +0 -34
- fractal_server/tasks/v2/_TaskCollectPip.py +0 -132
- {fractal_server-2.6.3.dist-info → fractal_server-2.7.0.dist-info}/LICENSE +0 -0
- {fractal_server-2.6.3.dist-info → fractal_server-2.7.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.6.3.dist-info → fractal_server-2.7.0.dist-info}/entry_points.txt +0 -0
@@ -2,17 +2,48 @@ from pathlib import Path
|
|
2
2
|
from typing import Optional
|
3
3
|
|
4
4
|
from ..utils import COLLECTION_FREEZE_FILENAME
|
5
|
+
from fractal_server.app.models.v2 import TaskGroupV2
|
5
6
|
from fractal_server.config import get_settings
|
6
7
|
from fractal_server.logger import get_logger
|
7
8
|
from fractal_server.syringe import Inject
|
8
|
-
from fractal_server.tasks.v2._TaskCollectPip import _TaskCollectPip
|
9
9
|
from fractal_server.tasks.v2.utils import get_python_interpreter_v2
|
10
10
|
from fractal_server.utils import execute_command
|
11
11
|
|
12
12
|
|
13
|
-
async def
|
13
|
+
async def _init_venv_v2(
|
14
|
+
*,
|
14
15
|
venv_path: Path,
|
15
|
-
|
16
|
+
python_version: Optional[str] = None,
|
17
|
+
logger_name: str,
|
18
|
+
) -> Path:
|
19
|
+
"""
|
20
|
+
Set a virtual environment at `path/venv`
|
21
|
+
|
22
|
+
Args:
|
23
|
+
path : Path
|
24
|
+
path to the venv actual directory (not its parent).
|
25
|
+
python_version : default=None
|
26
|
+
Python version the virtual environment will be based upon
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
python_bin : Path
|
30
|
+
path to python interpreter
|
31
|
+
"""
|
32
|
+
logger = get_logger(logger_name)
|
33
|
+
logger.debug(f"[_init_venv_v2] {venv_path=}")
|
34
|
+
interpreter = get_python_interpreter_v2(python_version=python_version)
|
35
|
+
logger.debug(f"[_init_venv_v2] {interpreter=}")
|
36
|
+
await execute_command(
|
37
|
+
command=f"{interpreter} -m venv {venv_path}",
|
38
|
+
logger_name=logger_name,
|
39
|
+
)
|
40
|
+
python_bin = venv_path / "bin/python"
|
41
|
+
logger.debug(f"[_init_venv_v2] {python_bin=}")
|
42
|
+
return python_bin
|
43
|
+
|
44
|
+
|
45
|
+
async def _pip_install(
|
46
|
+
task_group: TaskGroupV2,
|
16
47
|
logger_name: str,
|
17
48
|
) -> Path:
|
18
49
|
"""
|
@@ -30,48 +61,45 @@ async def _pip_install(
|
|
30
61
|
|
31
62
|
logger = get_logger(logger_name)
|
32
63
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
if task_pkg.is_local_package:
|
38
|
-
pip_install_str = f"{task_pkg.package_path.as_posix()}{extras}"
|
39
|
-
else:
|
40
|
-
version_string = (
|
41
|
-
f"=={task_pkg.package_version}" if task_pkg.package_version else ""
|
42
|
-
)
|
43
|
-
pip_install_str = f"{task_pkg.package_name}{extras}{version_string}"
|
64
|
+
python_bin = Path(task_group.venv_path) / "bin/python"
|
65
|
+
pip_install_str = task_group.pip_install_string
|
66
|
+
logger.info(f"{pip_install_str=}")
|
44
67
|
|
45
68
|
await execute_command(
|
46
|
-
cwd=venv_path,
|
69
|
+
cwd=Path(task_group.venv_path),
|
47
70
|
command=(
|
48
|
-
f"{
|
71
|
+
f"{python_bin} -m pip install --upgrade "
|
49
72
|
f"'pip<={settings.FRACTAL_MAX_PIP_VERSION}'"
|
50
73
|
),
|
51
74
|
logger_name=logger_name,
|
52
75
|
)
|
53
76
|
await execute_command(
|
54
|
-
cwd=venv_path,
|
55
|
-
command=f"{
|
77
|
+
cwd=Path(task_group.venv_path),
|
78
|
+
command=f"{python_bin} -m pip install setuptools",
|
79
|
+
logger_name=logger_name,
|
80
|
+
)
|
81
|
+
await execute_command(
|
82
|
+
cwd=Path(task_group.venv_path),
|
83
|
+
command=f"{python_bin} -m pip install {pip_install_str}",
|
56
84
|
logger_name=logger_name,
|
57
85
|
)
|
58
|
-
|
86
|
+
|
87
|
+
if task_group.pinned_package_versions:
|
59
88
|
for (
|
60
89
|
pinned_pkg_name,
|
61
90
|
pinned_pkg_version,
|
62
|
-
) in
|
63
|
-
|
91
|
+
) in task_group.pinned_package_versions.items():
|
64
92
|
logger.debug(
|
65
93
|
"Specific version required: "
|
66
94
|
f"{pinned_pkg_name}=={pinned_pkg_version}"
|
67
95
|
)
|
68
96
|
logger.debug(
|
69
97
|
"Preliminary check: verify that "
|
70
|
-
f"{
|
98
|
+
f"{pinned_pkg_name} is already installed"
|
71
99
|
)
|
72
100
|
stdout_show = await execute_command(
|
73
|
-
cwd=venv_path,
|
74
|
-
command=f"{
|
101
|
+
cwd=Path(task_group.venv_path),
|
102
|
+
command=f"{python_bin} -m pip show {pinned_pkg_name}",
|
75
103
|
logger_name=logger_name,
|
76
104
|
)
|
77
105
|
current_version = next(
|
@@ -87,9 +115,9 @@ async def _pip_install(
|
|
87
115
|
f"install version {pinned_pkg_version}."
|
88
116
|
)
|
89
117
|
await execute_command(
|
90
|
-
cwd=venv_path,
|
118
|
+
cwd=Path(task_group.venv_path),
|
91
119
|
command=(
|
92
|
-
f"{
|
120
|
+
f"{python_bin} -m pip install "
|
93
121
|
f"{pinned_pkg_name}=={pinned_pkg_version}"
|
94
122
|
),
|
95
123
|
logger_name=logger_name,
|
@@ -102,8 +130,8 @@ async def _pip_install(
|
|
102
130
|
|
103
131
|
# Extract package installation path from `pip show`
|
104
132
|
stdout_show = await execute_command(
|
105
|
-
cwd=venv_path,
|
106
|
-
command=f"{
|
133
|
+
cwd=Path(task_group.venv_path),
|
134
|
+
command=f"{python_bin} -m pip show {task_group.pkg_name}",
|
107
135
|
logger_name=logger_name,
|
108
136
|
)
|
109
137
|
|
@@ -124,58 +152,26 @@ async def _pip_install(
|
|
124
152
|
# characters with underscore (_) characters, so the .dist-info directory
|
125
153
|
# always has exactly one dash (-) character in its stem, separating the
|
126
154
|
# name and version fields.
|
127
|
-
package_root = location / (
|
155
|
+
package_root = location / (task_group.pkg_name.replace("-", "_"))
|
128
156
|
logger.debug(f"[_pip install] {location=}")
|
129
|
-
logger.debug(f"[_pip install] {
|
157
|
+
logger.debug(f"[_pip install] {task_group.pkg_name=}")
|
130
158
|
logger.debug(f"[_pip install] {package_root=}")
|
131
159
|
|
132
160
|
# Run `pip freeze --all` and store its output
|
133
161
|
stdout_freeze = await execute_command(
|
134
|
-
cwd=venv_path,
|
162
|
+
cwd=Path(task_group.venv_path),
|
163
|
+
command=f"{python_bin} -m pip freeze --all",
|
164
|
+
logger_name=logger_name,
|
135
165
|
)
|
136
|
-
with (
|
166
|
+
with (Path(task_group.path) / COLLECTION_FREEZE_FILENAME).open("w") as f:
|
137
167
|
f.write(stdout_freeze)
|
138
168
|
|
139
169
|
return package_root
|
140
170
|
|
141
171
|
|
142
|
-
async def _init_venv_v2(
|
143
|
-
*,
|
144
|
-
path: Path,
|
145
|
-
python_version: Optional[str] = None,
|
146
|
-
logger_name: str,
|
147
|
-
) -> Path:
|
148
|
-
"""
|
149
|
-
Set a virtual environment at `path/venv`
|
150
|
-
|
151
|
-
Args:
|
152
|
-
path : Path
|
153
|
-
path to directory in which to set up the virtual environment
|
154
|
-
python_version : default=None
|
155
|
-
Python version the virtual environment will be based upon
|
156
|
-
|
157
|
-
Returns:
|
158
|
-
python_bin : Path
|
159
|
-
path to python interpreter
|
160
|
-
"""
|
161
|
-
logger = get_logger(logger_name)
|
162
|
-
logger.debug(f"[_init_venv] {path=}")
|
163
|
-
interpreter = get_python_interpreter_v2(python_version=python_version)
|
164
|
-
logger.debug(f"[_init_venv] {interpreter=}")
|
165
|
-
await execute_command(
|
166
|
-
cwd=path,
|
167
|
-
command=f"{interpreter} -m venv venv",
|
168
|
-
logger_name=logger_name,
|
169
|
-
)
|
170
|
-
python_bin = path / "venv/bin/python"
|
171
|
-
logger.debug(f"[_init_venv] {python_bin=}")
|
172
|
-
return python_bin
|
173
|
-
|
174
|
-
|
175
172
|
async def _create_venv_install_package_pip(
|
176
173
|
*,
|
177
|
-
|
178
|
-
path: Path,
|
174
|
+
task_group: TaskGroupV2,
|
179
175
|
logger_name: str,
|
180
176
|
) -> tuple[Path, Path]:
|
181
177
|
"""
|
@@ -191,11 +187,12 @@ async def _create_venv_install_package_pip(
|
|
191
187
|
package_root: the location of the package manifest
|
192
188
|
"""
|
193
189
|
python_bin = await _init_venv_v2(
|
194
|
-
|
195
|
-
python_version=
|
190
|
+
venv_path=Path(task_group.venv_path),
|
191
|
+
python_version=task_group.python_version,
|
196
192
|
logger_name=logger_name,
|
197
193
|
)
|
198
194
|
package_root = await _pip_install(
|
199
|
-
|
195
|
+
task_group=task_group,
|
196
|
+
logger_name=logger_name,
|
200
197
|
)
|
201
198
|
return python_bin, package_root
|
@@ -5,21 +5,23 @@ 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
|
14
|
-
from ..utils import
|
15
|
-
from ..utils import get_collection_freeze
|
16
|
-
from ..utils import get_collection_log
|
17
|
+
from ..utils import get_collection_freeze_v2
|
18
|
+
from ..utils import get_collection_log_v2
|
17
19
|
from ..utils import get_collection_path
|
18
20
|
from ..utils import get_log_path
|
19
|
-
from .
|
21
|
+
from .database_operations import create_db_tasks_and_update_task_group
|
20
22
|
from fractal_server.app.db import get_sync_db
|
21
23
|
from fractal_server.app.models.v2 import CollectionStateV2
|
22
|
-
from fractal_server.app.models.v2 import
|
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
27
|
from fractal_server.app.schemas.v2 import TaskReadV2
|
@@ -28,38 +30,8 @@ from fractal_server.logger import get_logger
|
|
28
30
|
from fractal_server.logger import reset_logger_handlers
|
29
31
|
from fractal_server.logger import set_logger
|
30
32
|
from fractal_server.tasks.v2._venv_pip import _create_venv_install_package_pip
|
31
|
-
|
32
|
-
|
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"
|
40
|
-
|
41
|
-
|
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
|
49
|
-
"""
|
50
|
-
|
51
|
-
owner_dict = dict(owner=owner) if owner is not None else dict()
|
52
|
-
|
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
|
33
|
+
from fractal_server.tasks.v2.utils import get_python_interpreter_v2
|
34
|
+
from fractal_server.utils import execute_command
|
63
35
|
|
64
36
|
|
65
37
|
def _set_collection_state_data_status(
|
@@ -113,7 +85,8 @@ def _handle_failure(
|
|
113
85
|
logger_name: str,
|
114
86
|
exception: Exception,
|
115
87
|
db: DBSyncSession,
|
116
|
-
|
88
|
+
task_group_id: int,
|
89
|
+
path: Optional[Path] = None,
|
117
90
|
):
|
118
91
|
"""
|
119
92
|
Note: `venv_path` is only required to trigger the folder deletion.
|
@@ -144,11 +117,32 @@ def _handle_failure(
|
|
144
117
|
db=db,
|
145
118
|
)
|
146
119
|
# Delete corrupted package dir
|
147
|
-
if
|
148
|
-
logger.info(f"Now delete temporary folder {
|
149
|
-
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)
|
150
123
|
logger.info("Temporary folder deleted")
|
151
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
|
+
|
152
146
|
reset_logger_handlers(logger)
|
153
147
|
return
|
154
148
|
|
@@ -156,7 +150,6 @@ def _handle_failure(
|
|
156
150
|
def _prepare_tasks_metadata(
|
157
151
|
*,
|
158
152
|
package_manifest: ManifestV2,
|
159
|
-
package_source: str,
|
160
153
|
python_bin: Path,
|
161
154
|
package_root: Path,
|
162
155
|
package_version: Optional[str] = None,
|
@@ -166,7 +159,6 @@ def _prepare_tasks_metadata(
|
|
166
159
|
|
167
160
|
Args:
|
168
161
|
package_manifest:
|
169
|
-
package_source:
|
170
162
|
python_bin:
|
171
163
|
package_root:
|
172
164
|
package_version:
|
@@ -177,8 +169,6 @@ def _prepare_tasks_metadata(
|
|
177
169
|
task_attributes = {}
|
178
170
|
if package_version is not None:
|
179
171
|
task_attributes["version"] = package_version
|
180
|
-
task_name_slug = slugify_task_name_for_source(_task.name)
|
181
|
-
task_attributes["source"] = f"{package_source}:{task_name_slug}"
|
182
172
|
if package_manifest.has_args_schemas:
|
183
173
|
task_attributes[
|
184
174
|
"args_schema_version"
|
@@ -203,6 +193,7 @@ def _prepare_tasks_metadata(
|
|
203
193
|
}
|
204
194
|
),
|
205
195
|
**task_attributes,
|
196
|
+
authors=package_manifest.authors,
|
206
197
|
)
|
207
198
|
task_list.append(task_obj)
|
208
199
|
return task_list
|
@@ -231,10 +222,95 @@ def _check_task_files_exist(task_list: list[TaskCreateV2]) -> None:
|
|
231
222
|
)
|
232
223
|
|
233
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
|
+
|
234
310
|
async def background_collect_pip(
|
311
|
+
*,
|
235
312
|
state_id: int,
|
236
|
-
|
237
|
-
task_pkg: _TaskCollectPip,
|
313
|
+
task_group: TaskGroupV2,
|
238
314
|
) -> None:
|
239
315
|
"""
|
240
316
|
Setup venv, install package, collect tasks.
|
@@ -248,24 +324,48 @@ async def background_collect_pip(
|
|
248
324
|
5. Handle failures by copying the log into the state and deleting the
|
249
325
|
package directory.
|
250
326
|
"""
|
251
|
-
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
|
+
|
252
352
|
logger = set_logger(
|
253
353
|
logger_name=logger_name,
|
254
|
-
log_file_path=get_log_path(
|
354
|
+
log_file_path=get_log_path(Path(task_group.path)),
|
255
355
|
)
|
256
356
|
|
257
357
|
# Start
|
258
358
|
logger.debug("START")
|
259
|
-
for key, value in
|
260
|
-
logger.debug(f"
|
359
|
+
for key, value in task_group.model_dump().items():
|
360
|
+
logger.debug(f"task_group.{key}: {value}")
|
261
361
|
|
262
362
|
with next(get_sync_db()) as db:
|
263
|
-
|
264
363
|
try:
|
265
|
-
# Block 1:
|
266
|
-
|
267
|
-
|
268
|
-
|
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
|
+
)
|
269
369
|
|
270
370
|
# Block 2: create venv and run pip install
|
271
371
|
# Required: state_id, venv_path, task_pkg
|
@@ -277,8 +377,7 @@ async def background_collect_pip(
|
|
277
377
|
db=db,
|
278
378
|
)
|
279
379
|
python_bin, package_root = await _create_venv_install_package_pip(
|
280
|
-
|
281
|
-
task_pkg=task_pkg,
|
380
|
+
task_group=task_group,
|
282
381
|
logger_name=logger_name,
|
283
382
|
)
|
284
383
|
logger.debug("installing - END")
|
@@ -294,27 +393,38 @@ async def background_collect_pip(
|
|
294
393
|
)
|
295
394
|
logger.debug("collecting - prepare tasks and update db " "- START")
|
296
395
|
task_list = _prepare_tasks_metadata(
|
297
|
-
package_manifest=
|
298
|
-
package_version=
|
299
|
-
package_source=task_pkg.package_source,
|
396
|
+
package_manifest=pkg_manifest,
|
397
|
+
package_version=task_group.version,
|
300
398
|
package_root=package_root,
|
301
399
|
python_bin=python_bin,
|
302
400
|
)
|
303
401
|
_check_task_files_exist(task_list=task_list)
|
304
|
-
|
402
|
+
|
403
|
+
# Prepare some task-group attributes
|
404
|
+
task_group = create_db_tasks_and_update_task_group(
|
405
|
+
task_list=task_list,
|
406
|
+
task_group_id=task_group.id,
|
407
|
+
db=db,
|
408
|
+
)
|
409
|
+
|
305
410
|
logger.debug("collecting - prepare tasks and update db " "- END")
|
306
411
|
logger.debug("collecting - END")
|
307
412
|
|
308
413
|
# Block 4: finalize (write collection files, write metadata to DB)
|
309
414
|
logger.debug("finalising - START")
|
310
|
-
collection_path = get_collection_path(
|
415
|
+
collection_path = get_collection_path(Path(task_group.path))
|
311
416
|
collection_state = db.get(CollectionStateV2, state_id)
|
312
417
|
task_read_list = [
|
313
|
-
TaskReadV2(**task.model_dump()).dict()
|
418
|
+
TaskReadV2(**task.model_dump()).dict()
|
419
|
+
for task in task_group.task_list
|
314
420
|
]
|
315
421
|
collection_state.data["task_list"] = task_read_list
|
316
|
-
collection_state.data["log"] =
|
317
|
-
|
422
|
+
collection_state.data["log"] = get_collection_log_v2(
|
423
|
+
Path(task_group.path)
|
424
|
+
)
|
425
|
+
collection_state.data["freeze"] = get_collection_freeze_v2(
|
426
|
+
Path(task_group.path)
|
427
|
+
)
|
318
428
|
with collection_path.open("w") as f:
|
319
429
|
json.dump(collection_state.data, f, indent=2)
|
320
430
|
|
@@ -323,14 +433,15 @@ async def background_collect_pip(
|
|
323
433
|
logger.debug("finalising - END")
|
324
434
|
|
325
435
|
except Exception as e:
|
326
|
-
logfile_path = get_log_path(
|
436
|
+
logfile_path = get_log_path(Path(task_group.path))
|
327
437
|
_handle_failure(
|
328
438
|
state_id=state_id,
|
329
439
|
log_file_path=logfile_path,
|
330
440
|
logger_name=logger_name,
|
331
441
|
exception=e,
|
332
442
|
db=db,
|
333
|
-
|
443
|
+
path=task_group.path,
|
444
|
+
task_group_id=task_group.id,
|
334
445
|
)
|
335
446
|
return
|
336
447
|
|