fractal-server 2.7.0a3__py3-none-any.whl → 2.7.0a5__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 +4 -17
- fractal_server/app/routes/admin/v2/task_group.py +21 -0
- fractal_server/app/routes/api/v1/task_collection.py +4 -4
- fractal_server/app/routes/api/v2/_aux_functions.py +1 -7
- 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 +175 -204
- fractal_server/app/routes/api/v2/task_collection_custom.py +31 -58
- fractal_server/app/routes/api/v2/task_group.py +29 -1
- fractal_server/app/routes/api/v2/workflow.py +11 -46
- fractal_server/app/routes/api/v2/workflowtask.py +0 -1
- 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 -2
- 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 -10
- 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 -5
- 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/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 +62 -70
- fractal_server/tasks/v2/background_operations.py +170 -51
- 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 +91 -145
- 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.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/METADATA +1 -1
- {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/RECORD +49 -52
- 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.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/LICENSE +0 -0
- {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/WHEEL +0 -0
- {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/entry_points.txt +0 -0
fractal_server/images/models.py
CHANGED
@@ -3,6 +3,7 @@ from typing import Optional
|
|
3
3
|
from typing import Union
|
4
4
|
|
5
5
|
from pydantic import BaseModel
|
6
|
+
from pydantic import Extra
|
6
7
|
from pydantic import Field
|
7
8
|
from pydantic import validator
|
8
9
|
|
@@ -109,13 +110,10 @@ class SingleImageUpdate(BaseModel):
|
|
109
110
|
_types = validator("types", allow_reuse=True)(valdictkeys("types"))
|
110
111
|
|
111
112
|
|
112
|
-
class Filters(BaseModel):
|
113
|
+
class Filters(BaseModel, extra=Extra.forbid):
|
113
114
|
attributes: dict[str, Any] = Field(default_factory=dict)
|
114
115
|
types: dict[str, bool] = Field(default_factory=dict)
|
115
116
|
|
116
|
-
class Config:
|
117
|
-
extra = "forbid"
|
118
|
-
|
119
117
|
# Validators
|
120
118
|
_attributes = validator("attributes", allow_reuse=True)(
|
121
119
|
valdictkeys("attributes")
|
fractal_server/main.py
CHANGED
@@ -20,7 +20,7 @@ from contextlib import asynccontextmanager
|
|
20
20
|
|
21
21
|
from fastapi import FastAPI
|
22
22
|
|
23
|
-
from .app.routes.aux._runner import _backend_supports_shutdown
|
23
|
+
from .app.routes.aux._runner import _backend_supports_shutdown
|
24
24
|
from .app.runner.shutdown import cleanup_after_shutdown
|
25
25
|
from .config import get_settings
|
26
26
|
from .logger import config_uvicorn_loggers
|
@@ -0,0 +1,184 @@
|
|
1
|
+
"""task groups
|
2
|
+
|
3
|
+
Revision ID: 034a469ec2eb
|
4
|
+
Revises: da2cb2ac4255
|
5
|
+
Create Date: 2024-10-10 16:14:13.976231
|
6
|
+
|
7
|
+
"""
|
8
|
+
from datetime import datetime
|
9
|
+
from datetime import timezone
|
10
|
+
|
11
|
+
import sqlalchemy as sa
|
12
|
+
import sqlmodel
|
13
|
+
from alembic import op
|
14
|
+
|
15
|
+
|
16
|
+
# revision identifiers, used by Alembic.
|
17
|
+
revision = "034a469ec2eb"
|
18
|
+
down_revision = "da2cb2ac4255"
|
19
|
+
branch_labels = None
|
20
|
+
depends_on = None
|
21
|
+
|
22
|
+
|
23
|
+
def upgrade() -> None:
|
24
|
+
op.create_table(
|
25
|
+
"taskgroupv2",
|
26
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
27
|
+
sa.Column("user_id", sa.Integer(), nullable=False),
|
28
|
+
sa.Column("user_group_id", sa.Integer(), nullable=True),
|
29
|
+
sa.Column(
|
30
|
+
"origin", sqlmodel.sql.sqltypes.AutoString(), nullable=False
|
31
|
+
),
|
32
|
+
sa.Column(
|
33
|
+
"pkg_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False
|
34
|
+
),
|
35
|
+
sa.Column(
|
36
|
+
"version", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
37
|
+
),
|
38
|
+
sa.Column(
|
39
|
+
"python_version", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
40
|
+
),
|
41
|
+
sa.Column("path", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
42
|
+
sa.Column(
|
43
|
+
"venv_path", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
44
|
+
),
|
45
|
+
sa.Column(
|
46
|
+
"wheel_path", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
47
|
+
),
|
48
|
+
sa.Column(
|
49
|
+
"pip_extras", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
50
|
+
),
|
51
|
+
sa.Column(
|
52
|
+
"pinned_package_versions",
|
53
|
+
sa.JSON(),
|
54
|
+
server_default="{}",
|
55
|
+
nullable=True,
|
56
|
+
),
|
57
|
+
sa.Column("active", sa.Boolean(), nullable=False),
|
58
|
+
sa.Column(
|
59
|
+
"timestamp_created", sa.DateTime(timezone=True), nullable=False
|
60
|
+
),
|
61
|
+
sa.ForeignKeyConstraint(
|
62
|
+
["user_group_id"],
|
63
|
+
["usergroup.id"],
|
64
|
+
name=op.f("fk_taskgroupv2_user_group_id_usergroup"),
|
65
|
+
),
|
66
|
+
sa.ForeignKeyConstraint(
|
67
|
+
["user_id"],
|
68
|
+
["user_oauth.id"],
|
69
|
+
name=op.f("fk_taskgroupv2_user_id_user_oauth"),
|
70
|
+
),
|
71
|
+
sa.PrimaryKeyConstraint("id", name=op.f("pk_taskgroupv2")),
|
72
|
+
)
|
73
|
+
with op.batch_alter_table("collectionstatev2", schema=None) as batch_op:
|
74
|
+
batch_op.add_column(
|
75
|
+
sa.Column("taskgroupv2_id", sa.Integer(), nullable=True)
|
76
|
+
)
|
77
|
+
batch_op.create_foreign_key(
|
78
|
+
batch_op.f("fk_collectionstatev2_taskgroupv2_id_taskgroupv2"),
|
79
|
+
"taskgroupv2",
|
80
|
+
["taskgroupv2_id"],
|
81
|
+
["id"],
|
82
|
+
)
|
83
|
+
|
84
|
+
with op.batch_alter_table("linkusergroup", schema=None) as batch_op:
|
85
|
+
batch_op.add_column(
|
86
|
+
sa.Column(
|
87
|
+
"timestamp_created",
|
88
|
+
sa.DateTime(timezone=True),
|
89
|
+
nullable=False,
|
90
|
+
server_default=str(datetime(2000, 1, 1, tzinfo=timezone.utc)),
|
91
|
+
)
|
92
|
+
)
|
93
|
+
|
94
|
+
with op.batch_alter_table("taskv2", schema=None) as batch_op:
|
95
|
+
batch_op.add_column(
|
96
|
+
sa.Column("taskgroupv2_id", sa.Integer(), nullable=True)
|
97
|
+
)
|
98
|
+
batch_op.add_column(
|
99
|
+
sa.Column(
|
100
|
+
"category", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
101
|
+
)
|
102
|
+
)
|
103
|
+
batch_op.add_column(
|
104
|
+
sa.Column(
|
105
|
+
"modality", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
106
|
+
)
|
107
|
+
)
|
108
|
+
batch_op.add_column(
|
109
|
+
sa.Column(
|
110
|
+
"authors", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
111
|
+
)
|
112
|
+
)
|
113
|
+
batch_op.add_column(
|
114
|
+
sa.Column("tags", sa.JSON(), server_default="[]", nullable=False)
|
115
|
+
)
|
116
|
+
batch_op.alter_column(
|
117
|
+
"source", existing_type=sa.VARCHAR(), nullable=True
|
118
|
+
)
|
119
|
+
|
120
|
+
try:
|
121
|
+
with op.batch_alter_table("taskv2", schema=None) as batch_op:
|
122
|
+
batch_op.drop_constraint("uq_taskv2_source", type_="unique")
|
123
|
+
except BaseException as e:
|
124
|
+
if op.get_bind().dialect.name != "sqlite":
|
125
|
+
raise e
|
126
|
+
import sqlite3
|
127
|
+
import logging
|
128
|
+
|
129
|
+
logger = logging.getLogger("alembic.runtime.migration")
|
130
|
+
logger.warning(
|
131
|
+
f"Using sqlite, with {sqlite3.version=} and "
|
132
|
+
f"{sqlite3.sqlite_version=}"
|
133
|
+
)
|
134
|
+
|
135
|
+
logger.warning(
|
136
|
+
"Could not drop 'uq_taskv2_source' constraint; this is expected "
|
137
|
+
"when the database was created before the naming convention "
|
138
|
+
"was added."
|
139
|
+
)
|
140
|
+
logger.warning(
|
141
|
+
"As a workaround, we recreate the constraint before dropping it."
|
142
|
+
)
|
143
|
+
with op.batch_alter_table("taskv2", schema=None) as batch_op:
|
144
|
+
batch_op.create_unique_constraint("uq_taskv2_source", ["source"])
|
145
|
+
batch_op.drop_constraint("uq_taskv2_source", type_="unique")
|
146
|
+
|
147
|
+
with op.batch_alter_table("taskv2", schema=None) as batch_op:
|
148
|
+
batch_op.create_foreign_key(
|
149
|
+
batch_op.f("fk_taskv2_taskgroupv2_id_taskgroupv2"),
|
150
|
+
"taskgroupv2",
|
151
|
+
["taskgroupv2_id"],
|
152
|
+
["id"],
|
153
|
+
)
|
154
|
+
|
155
|
+
|
156
|
+
def downgrade() -> None:
|
157
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
158
|
+
with op.batch_alter_table("taskv2", schema=None) as batch_op:
|
159
|
+
batch_op.drop_constraint(
|
160
|
+
batch_op.f("fk_taskv2_taskgroupv2_id_taskgroupv2"),
|
161
|
+
type_="foreignkey",
|
162
|
+
)
|
163
|
+
batch_op.create_unique_constraint("uq_taskv2_source", ["source"])
|
164
|
+
batch_op.alter_column(
|
165
|
+
"source", existing_type=sa.VARCHAR(), nullable=False
|
166
|
+
)
|
167
|
+
batch_op.drop_column("tags")
|
168
|
+
batch_op.drop_column("authors")
|
169
|
+
batch_op.drop_column("modality")
|
170
|
+
batch_op.drop_column("category")
|
171
|
+
batch_op.drop_column("taskgroupv2_id")
|
172
|
+
|
173
|
+
with op.batch_alter_table("linkusergroup", schema=None) as batch_op:
|
174
|
+
batch_op.drop_column("timestamp_created")
|
175
|
+
|
176
|
+
with op.batch_alter_table("collectionstatev2", schema=None) as batch_op:
|
177
|
+
batch_op.drop_constraint(
|
178
|
+
batch_op.f("fk_collectionstatev2_taskgroupv2_id_taskgroupv2"),
|
179
|
+
type_="foreignkey",
|
180
|
+
)
|
181
|
+
batch_op.drop_column("taskgroupv2_id")
|
182
|
+
|
183
|
+
op.drop_table("taskgroupv2")
|
184
|
+
# ### end Alembic commands ###
|
fractal_server/string_tools.py
CHANGED
@@ -33,14 +33,18 @@ def sanitize_string(value: str) -> str:
|
|
33
33
|
return new_value
|
34
34
|
|
35
35
|
|
36
|
-
def
|
36
|
+
def slugify_task_name_for_source_v1(task_name: str) -> str:
|
37
37
|
"""
|
38
38
|
NOTE: this function is used upon creation of tasks' sources, therefore
|
39
39
|
for the moment we cannot replace it with its more comprehensive version
|
40
40
|
from `fractal_server.string_tools.sanitize_string`, nor we can remove it.
|
41
|
-
|
41
|
+
|
42
|
+
As of 2.3.1, we are renaming it to `slugify_task_name_for_source`, to make
|
42
43
|
it clear that it should not be used for other purposes.
|
43
44
|
|
45
|
+
As of 2.7.0, we are renaming it to `slugify_task_name_for_source_v1`, to
|
46
|
+
make it clear that it is not used for v2.
|
47
|
+
|
44
48
|
Args:
|
45
49
|
task_name:
|
46
50
|
|
fractal_server/tasks/utils.py
CHANGED
@@ -9,9 +9,11 @@ COLLECTION_LOG_FILENAME = "collection.log"
|
|
9
9
|
COLLECTION_FREEZE_FILENAME = "collection_freeze.txt"
|
10
10
|
|
11
11
|
|
12
|
-
def
|
12
|
+
def get_absolute_venv_path_v1(venv_path: Path) -> Path:
|
13
13
|
"""
|
14
14
|
If a path is not absolute, make it a relative path of FRACTAL_TASKS_DIR.
|
15
|
+
|
16
|
+
As of v2.7.0, we rename this to v1 since it is only to be used in v1.
|
15
17
|
"""
|
16
18
|
if venv_path.is_absolute():
|
17
19
|
package_path = venv_path
|
@@ -33,20 +35,32 @@ def get_freeze_path(base: Path) -> Path:
|
|
33
35
|
return base / COLLECTION_FREEZE_FILENAME
|
34
36
|
|
35
37
|
|
36
|
-
def
|
37
|
-
package_path =
|
38
|
+
def get_collection_log_v1(path: Path) -> str:
|
39
|
+
package_path = get_absolute_venv_path_v1(path)
|
38
40
|
log_path = get_log_path(package_path)
|
39
41
|
log = log_path.open().read()
|
40
42
|
return log
|
41
43
|
|
42
44
|
|
43
|
-
def
|
44
|
-
|
45
|
+
def get_collection_log_v2(path: Path) -> str:
|
46
|
+
log_path = get_log_path(path)
|
47
|
+
log = log_path.open().read()
|
48
|
+
return log
|
49
|
+
|
50
|
+
|
51
|
+
def get_collection_freeze_v1(venv_path: Path) -> str:
|
52
|
+
package_path = get_absolute_venv_path_v1(venv_path)
|
45
53
|
freeze_path = get_freeze_path(package_path)
|
46
54
|
freeze = freeze_path.open().read()
|
47
55
|
return freeze
|
48
56
|
|
49
57
|
|
58
|
+
def get_collection_freeze_v2(path: Path) -> str:
|
59
|
+
freeze_path = get_freeze_path(path)
|
60
|
+
freeze = freeze_path.open().read()
|
61
|
+
return freeze
|
62
|
+
|
63
|
+
|
50
64
|
def _normalize_package_name(name: str) -> str:
|
51
65
|
"""
|
52
66
|
Implement PyPa specifications for package-name normalization
|
@@ -6,9 +6,9 @@ import json
|
|
6
6
|
from pathlib import Path
|
7
7
|
from shutil import rmtree as shell_rmtree
|
8
8
|
|
9
|
-
from ...string_tools import
|
9
|
+
from ...string_tools import slugify_task_name_for_source_v1
|
10
10
|
from ..utils import _normalize_package_name
|
11
|
-
from ..utils import
|
11
|
+
from ..utils import get_collection_log_v1
|
12
12
|
from ..utils import get_collection_path
|
13
13
|
from ..utils import get_log_path
|
14
14
|
from ._TaskCollectPip import _TaskCollectPip
|
@@ -215,7 +215,7 @@ async def create_package_environment_pip(
|
|
215
215
|
# Fill in attributes for TaskCreate
|
216
216
|
task_executable = package_root / t.executable
|
217
217
|
cmd = f"{python_bin.as_posix()} {task_executable.as_posix()}"
|
218
|
-
task_name_slug =
|
218
|
+
task_name_slug = slugify_task_name_for_source_v1(t.name)
|
219
219
|
task_source = f"{task_pkg.package_source}:{task_name_slug}"
|
220
220
|
if not task_executable.exists():
|
221
221
|
raise FileNotFoundError(
|
@@ -321,7 +321,7 @@ async def background_collect_pip(
|
|
321
321
|
|
322
322
|
# Update DB
|
323
323
|
data.status = "OK"
|
324
|
-
data.log =
|
324
|
+
data.log = get_collection_log_v1(venv_path)
|
325
325
|
state.data = data.sanitised_dict()
|
326
326
|
db.add(state)
|
327
327
|
db.merge(state)
|
@@ -342,7 +342,7 @@ async def background_collect_pip(
|
|
342
342
|
# Update db
|
343
343
|
data.status = "fail"
|
344
344
|
data.info = f"Original error: {e}"
|
345
|
-
data.log =
|
345
|
+
data.log = get_collection_log_v1(venv_path)
|
346
346
|
state.data = data.sanitised_dict()
|
347
347
|
db.merge(state)
|
348
348
|
db.commit()
|
@@ -2,12 +2,12 @@ import json
|
|
2
2
|
from pathlib import Path
|
3
3
|
|
4
4
|
from fractal_server.app.schemas.v1 import TaskCollectStatusV1
|
5
|
-
from fractal_server.tasks.utils import
|
5
|
+
from fractal_server.tasks.utils import get_absolute_venv_path_v1
|
6
6
|
from fractal_server.tasks.utils import get_collection_path
|
7
7
|
|
8
8
|
|
9
9
|
def get_collection_data(venv_path: Path) -> TaskCollectStatusV1:
|
10
|
-
package_path =
|
10
|
+
package_path = get_absolute_venv_path_v1(venv_path)
|
11
11
|
collection_path = get_collection_path(package_path)
|
12
12
|
with collection_path.open() as f:
|
13
13
|
data = json.load(f)
|
@@ -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,40 @@ 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 {pip_install_str}",
|
56
79
|
logger_name=logger_name,
|
57
80
|
)
|
58
|
-
|
81
|
+
|
82
|
+
if task_group.pinned_package_versions:
|
59
83
|
for (
|
60
84
|
pinned_pkg_name,
|
61
85
|
pinned_pkg_version,
|
62
|
-
) in
|
63
|
-
|
86
|
+
) in task_group.pinned_package_versions.items():
|
64
87
|
logger.debug(
|
65
88
|
"Specific version required: "
|
66
89
|
f"{pinned_pkg_name}=={pinned_pkg_version}"
|
67
90
|
)
|
68
91
|
logger.debug(
|
69
92
|
"Preliminary check: verify that "
|
70
|
-
f"{
|
93
|
+
f"{pinned_pkg_name} is already installed"
|
71
94
|
)
|
72
95
|
stdout_show = await execute_command(
|
73
|
-
cwd=venv_path,
|
74
|
-
command=f"{
|
96
|
+
cwd=Path(task_group.venv_path),
|
97
|
+
command=f"{python_bin} -m pip show {pinned_pkg_name}",
|
75
98
|
logger_name=logger_name,
|
76
99
|
)
|
77
100
|
current_version = next(
|
@@ -87,9 +110,9 @@ async def _pip_install(
|
|
87
110
|
f"install version {pinned_pkg_version}."
|
88
111
|
)
|
89
112
|
await execute_command(
|
90
|
-
cwd=venv_path,
|
113
|
+
cwd=Path(task_group.venv_path),
|
91
114
|
command=(
|
92
|
-
f"{
|
115
|
+
f"{python_bin} -m pip install "
|
93
116
|
f"{pinned_pkg_name}=={pinned_pkg_version}"
|
94
117
|
),
|
95
118
|
logger_name=logger_name,
|
@@ -102,8 +125,8 @@ async def _pip_install(
|
|
102
125
|
|
103
126
|
# Extract package installation path from `pip show`
|
104
127
|
stdout_show = await execute_command(
|
105
|
-
cwd=venv_path,
|
106
|
-
command=f"{
|
128
|
+
cwd=Path(task_group.venv_path),
|
129
|
+
command=f"{python_bin} -m pip show {task_group.pkg_name}",
|
107
130
|
logger_name=logger_name,
|
108
131
|
)
|
109
132
|
|
@@ -124,58 +147,26 @@ async def _pip_install(
|
|
124
147
|
# characters with underscore (_) characters, so the .dist-info directory
|
125
148
|
# always has exactly one dash (-) character in its stem, separating the
|
126
149
|
# name and version fields.
|
127
|
-
package_root = location / (
|
150
|
+
package_root = location / (task_group.pkg_name.replace("-", "_"))
|
128
151
|
logger.debug(f"[_pip install] {location=}")
|
129
|
-
logger.debug(f"[_pip install] {
|
152
|
+
logger.debug(f"[_pip install] {task_group.pkg_name=}")
|
130
153
|
logger.debug(f"[_pip install] {package_root=}")
|
131
154
|
|
132
155
|
# Run `pip freeze --all` and store its output
|
133
156
|
stdout_freeze = await execute_command(
|
134
|
-
cwd=venv_path,
|
157
|
+
cwd=Path(task_group.venv_path),
|
158
|
+
command=f"{python_bin} -m pip freeze --all",
|
159
|
+
logger_name=logger_name,
|
135
160
|
)
|
136
|
-
with (
|
161
|
+
with (Path(task_group.path) / COLLECTION_FREEZE_FILENAME).open("w") as f:
|
137
162
|
f.write(stdout_freeze)
|
138
163
|
|
139
164
|
return package_root
|
140
165
|
|
141
166
|
|
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
167
|
async def _create_venv_install_package_pip(
|
176
168
|
*,
|
177
|
-
|
178
|
-
path: Path,
|
169
|
+
task_group: TaskGroupV2,
|
179
170
|
logger_name: str,
|
180
171
|
) -> tuple[Path, Path]:
|
181
172
|
"""
|
@@ -191,11 +182,12 @@ async def _create_venv_install_package_pip(
|
|
191
182
|
package_root: the location of the package manifest
|
192
183
|
"""
|
193
184
|
python_bin = await _init_venv_v2(
|
194
|
-
|
195
|
-
python_version=
|
185
|
+
venv_path=Path(task_group.venv_path),
|
186
|
+
python_version=task_group.python_version,
|
196
187
|
logger_name=logger_name,
|
197
188
|
)
|
198
189
|
package_root = await _pip_install(
|
199
|
-
|
190
|
+
task_group=task_group,
|
191
|
+
logger_name=logger_name,
|
200
192
|
)
|
201
193
|
return python_bin, package_root
|