fractal-server 2.7.0a10__py3-none-any.whl → 2.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fractal_server/__init__.py +1 -1
- fractal_server/app/models/v2/task.py +1 -2
- fractal_server/app/routes/admin/v2/task_group.py +7 -0
- fractal_server/app/routes/api/v2/task.py +2 -1
- fractal_server/app/routes/api/v2/task_collection.py +3 -2
- fractal_server/app/routes/api/v2/task_collection_custom.py +2 -1
- fractal_server/app/routes/api/v2/workflow_import.py +4 -1
- fractal_server/app/runner/executors/slurm/ssh/executor.py +72 -51
- fractal_server/app/schemas/v2/__init__.py +1 -0
- fractal_server/app/schemas/v2/task_group.py +8 -1
- fractal_server/app/security/__init__.py +8 -1
- fractal_server/config.py +8 -28
- fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +42 -0
- fractal_server/tasks/v2/_venv_pip.py +5 -0
- fractal_server/tasks/v2/background_operations_ssh.py +1 -1
- fractal_server/tasks/v2/templates/{_2_upgrade_pip.sh → _2_preliminary_pip_operations.sh} +1 -0
- {fractal_server-2.7.0a10.dist-info → fractal_server-2.7.1.dist-info}/METADATA +4 -10
- {fractal_server-2.7.0a10.dist-info → fractal_server-2.7.1.dist-info}/RECORD +21 -21
- fractal_server/data_migrations/2_7_0.py +0 -323
- {fractal_server-2.7.0a10.dist-info → fractal_server-2.7.1.dist-info}/LICENSE +0 -0
- {fractal_server-2.7.0a10.dist-info → fractal_server-2.7.1.dist-info}/WHEEL +0 -0
- {fractal_server-2.7.0a10.dist-info → fractal_server-2.7.1.dist-info}/entry_points.txt +0 -0
fractal_server/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "2.7.
|
1
|
+
__VERSION__ = "2.7.1"
|
@@ -29,7 +29,6 @@ class TaskV2(SQLModel, table=True):
|
|
29
29
|
sa_column=Column(JSON, server_default="{}", default={}, nullable=False)
|
30
30
|
)
|
31
31
|
|
32
|
-
owner: Optional[str] = None
|
33
32
|
version: Optional[str] = None
|
34
33
|
args_schema_non_parallel: Optional[dict[str, Any]] = Field(
|
35
34
|
sa_column=Column(JSON), default=None
|
@@ -44,7 +43,7 @@ class TaskV2(SQLModel, table=True):
|
|
44
43
|
input_types: dict[str, bool] = Field(sa_column=Column(JSON), default={})
|
45
44
|
output_types: dict[str, bool] = Field(sa_column=Column(JSON), default={})
|
46
45
|
|
47
|
-
taskgroupv2_id:
|
46
|
+
taskgroupv2_id: int = Field(foreign_key="taskgroupv2.id")
|
48
47
|
|
49
48
|
category: Optional[str] = None
|
50
49
|
modality: Optional[str] = None
|
@@ -21,6 +21,7 @@ from fractal_server.app.routes.auth._aux_auth import (
|
|
21
21
|
)
|
22
22
|
from fractal_server.app.schemas.v2 import TaskGroupReadV2
|
23
23
|
from fractal_server.app.schemas.v2 import TaskGroupUpdateV2
|
24
|
+
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
24
25
|
from fractal_server.logger import set_logger
|
25
26
|
|
26
27
|
router = APIRouter()
|
@@ -50,6 +51,8 @@ async def query_task_group_list(
|
|
50
51
|
user_group_id: Optional[int] = None,
|
51
52
|
private: Optional[bool] = None,
|
52
53
|
active: Optional[bool] = None,
|
54
|
+
pkg_name: Optional[str] = None,
|
55
|
+
origin: Optional[TaskGroupV2OriginEnum] = None,
|
53
56
|
user: UserOAuth = Depends(current_active_superuser),
|
54
57
|
db: AsyncSession = Depends(get_async_db),
|
55
58
|
) -> list[TaskGroupReadV2]:
|
@@ -75,6 +78,10 @@ async def query_task_group_list(
|
|
75
78
|
stm = stm.where(is_(TaskGroupV2.active, True))
|
76
79
|
else:
|
77
80
|
stm = stm.where(is_(TaskGroupV2.active, False))
|
81
|
+
if origin is not None:
|
82
|
+
stm = stm.where(TaskGroupV2.origin == origin)
|
83
|
+
if pkg_name is not None:
|
84
|
+
stm = stm.where(TaskGroupV2.pkg_name.icontains(pkg_name))
|
78
85
|
|
79
86
|
res = await db.execute(stm)
|
80
87
|
task_groups_list = res.scalars().all()
|
@@ -24,6 +24,7 @@ from fractal_server.app.models.v2 import TaskV2
|
|
24
24
|
from fractal_server.app.routes.auth import current_active_user
|
25
25
|
from fractal_server.app.routes.auth import current_active_verified_user
|
26
26
|
from fractal_server.app.schemas.v2 import TaskCreateV2
|
27
|
+
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
27
28
|
from fractal_server.app.schemas.v2 import TaskReadV2
|
28
29
|
from fractal_server.app.schemas.v2 import TaskUpdateV2
|
29
30
|
from fractal_server.logger import set_logger
|
@@ -200,7 +201,7 @@ async def create_task(
|
|
200
201
|
user_group_id=user_group_id,
|
201
202
|
active=True,
|
202
203
|
task_list=[db_task],
|
203
|
-
origin=
|
204
|
+
origin=TaskGroupV2OriginEnum.OTHER,
|
204
205
|
version=db_task.version,
|
205
206
|
pkg_name=pkg_name,
|
206
207
|
)
|
@@ -30,6 +30,7 @@ from ._aux_functions_tasks import _verify_non_duplication_user_constraint
|
|
30
30
|
from fractal_server.app.models import UserOAuth
|
31
31
|
from fractal_server.app.routes.auth import current_active_user
|
32
32
|
from fractal_server.app.routes.auth import current_active_verified_user
|
33
|
+
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
33
34
|
from fractal_server.tasks.utils import _normalize_package_name
|
34
35
|
from fractal_server.tasks.utils import get_collection_log_v2
|
35
36
|
from fractal_server.tasks.v2.background_operations import (
|
@@ -121,11 +122,11 @@ async def collect_tasks_pip(
|
|
121
122
|
wheel_info["distribution"]
|
122
123
|
)
|
123
124
|
task_group_attrs["version"] = wheel_info["version"]
|
124
|
-
task_group_attrs["origin"] =
|
125
|
+
task_group_attrs["origin"] = TaskGroupV2OriginEnum.WHEELFILE
|
125
126
|
else:
|
126
127
|
pkg_name = task_collect.package
|
127
128
|
task_group_attrs["pkg_name"] = _normalize_package_name(pkg_name)
|
128
|
-
task_group_attrs["origin"] =
|
129
|
+
task_group_attrs["origin"] = TaskGroupV2OriginEnum.PYPI
|
129
130
|
latest_version = await get_package_version_from_pypi(
|
130
131
|
task_collect.package,
|
131
132
|
task_collect.package_version,
|
@@ -21,6 +21,7 @@ from fractal_server.app.routes.auth import current_active_verified_user
|
|
21
21
|
from fractal_server.app.schemas.v2 import TaskCollectCustomV2
|
22
22
|
from fractal_server.app.schemas.v2 import TaskCreateV2
|
23
23
|
from fractal_server.app.schemas.v2 import TaskGroupCreateV2
|
24
|
+
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
24
25
|
from fractal_server.app.schemas.v2 import TaskReadV2
|
25
26
|
from fractal_server.config import get_settings
|
26
27
|
from fractal_server.logger import set_logger
|
@@ -140,7 +141,7 @@ async def collect_task_custom(
|
|
140
141
|
|
141
142
|
# Prepare task-group attributes
|
142
143
|
task_group_attrs = dict(
|
143
|
-
origin=
|
144
|
+
origin=TaskGroupV2OriginEnum.OTHER,
|
144
145
|
pkg_name=task_collect.label,
|
145
146
|
user_id=user.id,
|
146
147
|
user_group_id=user_group_id,
|
@@ -241,7 +241,10 @@ async def _get_task_by_taskimport(
|
|
241
241
|
"Found many task groups, after filtering by version."
|
242
242
|
)
|
243
243
|
final_task_group = await _disambiguate_task_groups(
|
244
|
-
matching_task_groups,
|
244
|
+
matching_task_groups=matching_task_groups,
|
245
|
+
user_id=user_id,
|
246
|
+
db=db,
|
247
|
+
default_group_id=default_group_id,
|
245
248
|
)
|
246
249
|
if final_task_group is None:
|
247
250
|
logger.info(
|
@@ -1055,55 +1055,59 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
|
|
1055
1055
|
Arguments:
|
1056
1056
|
jobid: ID of the SLURM job
|
1057
1057
|
"""
|
1058
|
-
|
1059
|
-
# Loop over all job_ids, and fetch future and job objects
|
1060
|
-
futures: list[Future] = []
|
1061
|
-
jobs: list[SlurmJob] = []
|
1062
|
-
with self.jobs_lock:
|
1063
|
-
for job_id in job_ids:
|
1064
|
-
future, job = self.jobs.pop(job_id)
|
1065
|
-
futures.append(future)
|
1066
|
-
jobs.append(job)
|
1067
|
-
if not self.jobs:
|
1068
|
-
self.jobs_empty_cond.notify_all()
|
1069
|
-
|
1070
|
-
# Fetch subfolder from remote host
|
1058
|
+
# Handle all uncaught exceptions in this broad try/except block
|
1071
1059
|
try:
|
1072
|
-
self._get_subfolder_sftp(jobs=jobs)
|
1073
|
-
except NoValidConnectionsError as e:
|
1074
|
-
logger.error("NoValidConnectionError")
|
1075
|
-
logger.error(f"{str(e)=}")
|
1076
|
-
logger.error(f"{e.errors=}")
|
1077
|
-
for err in e.errors:
|
1078
|
-
logger.error(f"{str(err)}")
|
1079
|
-
|
1080
|
-
raise e
|
1081
|
-
|
1082
|
-
# First round of checking whether all output files exist
|
1083
|
-
missing_out_paths = []
|
1084
|
-
for job in jobs:
|
1085
|
-
for ind_out_path, out_path in enumerate(
|
1086
|
-
job.output_pickle_files_local
|
1087
|
-
):
|
1088
|
-
if not out_path.exists():
|
1089
|
-
missing_out_paths.append(out_path)
|
1090
|
-
num_missing = len(missing_out_paths)
|
1091
|
-
if num_missing > 0:
|
1092
|
-
# Output pickle files may be missing e.g. because of some slow
|
1093
|
-
# filesystem operation; wait some time before re-trying
|
1094
|
-
settings = Inject(get_settings)
|
1095
|
-
sleep_time = settings.FRACTAL_SLURM_ERROR_HANDLING_INTERVAL
|
1096
1060
|
logger.info(
|
1097
|
-
f"
|
1098
|
-
f"sleep {sleep_time} seconds."
|
1061
|
+
f"[FractalSlurmSSHExecutor._completion] START, for {job_ids=}."
|
1099
1062
|
)
|
1100
|
-
for missing_file in missing_out_paths:
|
1101
|
-
logger.debug(f"Missing output pickle file: {missing_file}")
|
1102
|
-
time.sleep(sleep_time)
|
1103
1063
|
|
1104
|
-
|
1105
|
-
|
1064
|
+
# Loop over all job_ids, and fetch future and job objects
|
1065
|
+
futures: list[Future] = []
|
1066
|
+
jobs: list[SlurmJob] = []
|
1067
|
+
with self.jobs_lock:
|
1068
|
+
for job_id in job_ids:
|
1069
|
+
future, job = self.jobs.pop(job_id)
|
1070
|
+
futures.append(future)
|
1071
|
+
jobs.append(job)
|
1072
|
+
if not self.jobs:
|
1073
|
+
self.jobs_empty_cond.notify_all()
|
1074
|
+
|
1075
|
+
# Fetch subfolder from remote host
|
1106
1076
|
try:
|
1077
|
+
self._get_subfolder_sftp(jobs=jobs)
|
1078
|
+
except NoValidConnectionsError as e:
|
1079
|
+
logger.error("NoValidConnectionError")
|
1080
|
+
logger.error(f"{str(e)=}")
|
1081
|
+
logger.error(f"{e.errors=}")
|
1082
|
+
for err in e.errors:
|
1083
|
+
logger.error(f"{str(err)}")
|
1084
|
+
|
1085
|
+
raise e
|
1086
|
+
|
1087
|
+
# First round of checking whether all output files exist
|
1088
|
+
missing_out_paths = []
|
1089
|
+
for job in jobs:
|
1090
|
+
for ind_out_path, out_path in enumerate(
|
1091
|
+
job.output_pickle_files_local
|
1092
|
+
):
|
1093
|
+
if not out_path.exists():
|
1094
|
+
missing_out_paths.append(out_path)
|
1095
|
+
num_missing = len(missing_out_paths)
|
1096
|
+
if num_missing > 0:
|
1097
|
+
# Output pickle files may be missing e.g. because of some slow
|
1098
|
+
# filesystem operation; wait some time before re-trying
|
1099
|
+
settings = Inject(get_settings)
|
1100
|
+
sleep_time = settings.FRACTAL_SLURM_ERROR_HANDLING_INTERVAL
|
1101
|
+
logger.info(
|
1102
|
+
f"{num_missing} output pickle files are missing; "
|
1103
|
+
f"sleep {sleep_time} seconds."
|
1104
|
+
)
|
1105
|
+
for missing_file in missing_out_paths:
|
1106
|
+
logger.debug(f"Missing output pickle file: {missing_file}")
|
1107
|
+
time.sleep(sleep_time)
|
1108
|
+
|
1109
|
+
# Handle all jobs
|
1110
|
+
for ind_job, job_id in enumerate(job_ids):
|
1107
1111
|
# Retrieve job and future objects
|
1108
1112
|
job = jobs[ind_job]
|
1109
1113
|
future = futures[ind_job]
|
@@ -1128,6 +1132,11 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
|
|
1128
1132
|
remaining_futures=remaining_futures,
|
1129
1133
|
remaining_job_ids=remaining_job_ids,
|
1130
1134
|
)
|
1135
|
+
logger.info(
|
1136
|
+
"[FractalSlurmSSHExecutor._completion] END, "
|
1137
|
+
f"for {job_ids=}, with JobExecutionError due "
|
1138
|
+
f"to missing {out_path.as_posix()}."
|
1139
|
+
)
|
1131
1140
|
return
|
1132
1141
|
except InvalidStateError:
|
1133
1142
|
logger.warning(
|
@@ -1141,6 +1150,12 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
|
|
1141
1150
|
remaining_futures=remaining_futures,
|
1142
1151
|
remaining_job_ids=remaining_job_ids,
|
1143
1152
|
)
|
1153
|
+
logger.info(
|
1154
|
+
"[FractalSlurmSSHExecutor._completion] END, "
|
1155
|
+
f"for {job_ids=}, with JobExecutionError/"
|
1156
|
+
"InvalidStateError due to "
|
1157
|
+
f"missing {out_path.as_posix()}."
|
1158
|
+
)
|
1144
1159
|
return
|
1145
1160
|
|
1146
1161
|
# Read the task output
|
@@ -1217,16 +1232,22 @@ class FractalSlurmSSHExecutor(SlurmExecutor):
|
|
1217
1232
|
else:
|
1218
1233
|
future.set_result(outputs)
|
1219
1234
|
|
1220
|
-
|
1235
|
+
except Exception as e:
|
1236
|
+
logger.warning(
|
1237
|
+
"[FractalSlurmSSHExecutor._completion] "
|
1238
|
+
f"An exception took place: {str(e)}."
|
1239
|
+
)
|
1240
|
+
for future in futures:
|
1221
1241
|
try:
|
1242
|
+
logger.info(f"Set exception for {future=}")
|
1222
1243
|
future.set_exception(e)
|
1223
|
-
return
|
1224
1244
|
except InvalidStateError:
|
1225
|
-
logger.
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1245
|
+
logger.info(f"Future {future} was already cancelled.")
|
1246
|
+
logger.info(
|
1247
|
+
f"[FractalSlurmSSHExecutor._completion] END, for {job_ids=}, "
|
1248
|
+
"from within exception handling."
|
1249
|
+
)
|
1250
|
+
return
|
1230
1251
|
|
1231
1252
|
def _get_subfolder_sftp(self, jobs: list[SlurmJob]) -> None:
|
1232
1253
|
"""
|
@@ -30,6 +30,7 @@ from .task_collection import TaskCollectPipV2 # noqa F401
|
|
30
30
|
from .task_group import TaskGroupCreateV2 # noqa F401
|
31
31
|
from .task_group import TaskGroupReadV2 # noqa F401
|
32
32
|
from .task_group import TaskGroupUpdateV2 # noqa F401
|
33
|
+
from .task_group import TaskGroupV2OriginEnum # noqa F401
|
33
34
|
from .workflow import WorkflowCreateV2 # noqa F401
|
34
35
|
from .workflow import WorkflowExportV2 # noqa F401
|
35
36
|
from .workflow import WorkflowImportV2 # noqa F401
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from datetime import datetime
|
2
|
+
from enum import Enum
|
2
3
|
from typing import Literal
|
3
4
|
from typing import Optional
|
4
5
|
|
@@ -13,11 +14,17 @@ from .._validators import valstr
|
|
13
14
|
from .task import TaskReadV2
|
14
15
|
|
15
16
|
|
17
|
+
class TaskGroupV2OriginEnum(str, Enum):
|
18
|
+
PYPI = "pypi"
|
19
|
+
WHEELFILE = "wheel-file"
|
20
|
+
OTHER = "other"
|
21
|
+
|
22
|
+
|
16
23
|
class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
|
17
24
|
user_id: int
|
18
25
|
user_group_id: Optional[int] = None
|
19
26
|
active: bool = True
|
20
|
-
origin:
|
27
|
+
origin: TaskGroupV2OriginEnum
|
21
28
|
pkg_name: str
|
22
29
|
version: Optional[str] = None
|
23
30
|
python_version: Optional[str] = None
|
@@ -315,13 +315,20 @@ async def _create_first_user(
|
|
315
315
|
|
316
316
|
|
317
317
|
def _create_first_group():
|
318
|
+
"""
|
319
|
+
Create a `UserGroup` with `name=FRACTAL_DEFAULT_GROUP_NAME`, if missing.
|
320
|
+
"""
|
318
321
|
function_logger = set_logger("fractal_server.create_first_group")
|
319
322
|
|
320
323
|
function_logger.info(
|
321
324
|
f"START _create_first_group, with name '{FRACTAL_DEFAULT_GROUP_NAME}'"
|
322
325
|
)
|
323
326
|
with next(get_sync_db()) as db:
|
324
|
-
group_all = db.execute(
|
327
|
+
group_all = db.execute(
|
328
|
+
select(UserGroup).where(
|
329
|
+
UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
|
330
|
+
)
|
331
|
+
)
|
325
332
|
if group_all.scalars().one_or_none() is None:
|
326
333
|
first_group = UserGroup(name=FRACTAL_DEFAULT_GROUP_NAME)
|
327
334
|
db.add(first_group)
|
fractal_server/config.py
CHANGED
@@ -167,9 +167,9 @@ class Settings(BaseSettings):
|
|
167
167
|
###########################################################################
|
168
168
|
# DATABASE
|
169
169
|
###########################################################################
|
170
|
-
DB_ENGINE: Literal["sqlite", "postgres
|
170
|
+
DB_ENGINE: Literal["sqlite", "postgres-psycopg"] = "sqlite"
|
171
171
|
"""
|
172
|
-
|
172
|
+
Database engine to use (supported: `sqlite`, `postgres-psycopg`).
|
173
173
|
"""
|
174
174
|
DB_ECHO: bool = False
|
175
175
|
"""
|
@@ -203,16 +203,7 @@ class Settings(BaseSettings):
|
|
203
203
|
|
204
204
|
@property
|
205
205
|
def DATABASE_ASYNC_URL(self) -> URL:
|
206
|
-
if self.DB_ENGINE == "postgres":
|
207
|
-
url = URL.create(
|
208
|
-
drivername="postgresql+asyncpg",
|
209
|
-
username=self.POSTGRES_USER,
|
210
|
-
password=self.POSTGRES_PASSWORD,
|
211
|
-
host=self.POSTGRES_HOST,
|
212
|
-
port=self.POSTGRES_PORT,
|
213
|
-
database=self.POSTGRES_DB,
|
214
|
-
)
|
215
|
-
elif self.DB_ENGINE == "postgres-psycopg":
|
206
|
+
if self.DB_ENGINE == "postgres-psycopg":
|
216
207
|
url = URL.create(
|
217
208
|
drivername="postgresql+psycopg",
|
218
209
|
username=self.POSTGRES_USER,
|
@@ -235,11 +226,7 @@ class Settings(BaseSettings):
|
|
235
226
|
|
236
227
|
@property
|
237
228
|
def DATABASE_SYNC_URL(self):
|
238
|
-
if self.DB_ENGINE == "postgres":
|
239
|
-
return self.DATABASE_ASYNC_URL.set(
|
240
|
-
drivername="postgresql+psycopg2"
|
241
|
-
)
|
242
|
-
elif self.DB_ENGINE == "postgres-psycopg":
|
229
|
+
if self.DB_ENGINE == "postgres-psycopg":
|
243
230
|
return self.DATABASE_ASYNC_URL.set(drivername="postgresql+psycopg")
|
244
231
|
else:
|
245
232
|
if not self.SQLITE_PATH:
|
@@ -546,20 +533,13 @@ class Settings(BaseSettings):
|
|
546
533
|
"""
|
547
534
|
Checks that db environment variables are properly set.
|
548
535
|
"""
|
549
|
-
if self.DB_ENGINE == "postgres":
|
536
|
+
if self.DB_ENGINE == "postgres-psycopg":
|
550
537
|
if not self.POSTGRES_DB:
|
551
538
|
raise FractalConfigurationError(
|
552
|
-
"POSTGRES_DB cannot be None when DB_ENGINE=
|
553
|
-
|
554
|
-
try:
|
555
|
-
import psycopg2 # noqa: F401
|
556
|
-
import asyncpg # noqa: F401
|
557
|
-
except ModuleNotFoundError:
|
558
|
-
raise FractalConfigurationError(
|
559
|
-
"DB engine is `postgres` but `psycopg2` or `asyncpg` "
|
560
|
-
"are not available"
|
539
|
+
"POSTGRES_DB cannot be None when DB_ENGINE="
|
540
|
+
"postgres-psycopg."
|
561
541
|
)
|
562
|
-
|
542
|
+
|
563
543
|
try:
|
564
544
|
import psycopg # noqa: F401
|
565
545
|
except ModuleNotFoundError:
|
@@ -0,0 +1,42 @@
|
|
1
|
+
"""Update TaskV2 post 2.7.0
|
2
|
+
|
3
|
+
Revision ID: 8e8f227a3e36
|
4
|
+
Revises: 034a469ec2eb
|
5
|
+
Create Date: 2024-10-29 09:01:33.075251
|
6
|
+
|
7
|
+
"""
|
8
|
+
import sqlalchemy as sa
|
9
|
+
from alembic import op
|
10
|
+
|
11
|
+
|
12
|
+
# revision identifiers, used by Alembic.
|
13
|
+
revision = "8e8f227a3e36"
|
14
|
+
down_revision = "034a469ec2eb"
|
15
|
+
branch_labels = None
|
16
|
+
depends_on = None
|
17
|
+
|
18
|
+
|
19
|
+
def upgrade() -> None:
|
20
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
21
|
+
with op.batch_alter_table("taskv2", schema=None) as batch_op:
|
22
|
+
batch_op.alter_column(
|
23
|
+
"taskgroupv2_id", existing_type=sa.INTEGER(), nullable=False
|
24
|
+
)
|
25
|
+
batch_op.drop_column("owner")
|
26
|
+
|
27
|
+
# ### end Alembic commands ###
|
28
|
+
|
29
|
+
|
30
|
+
def downgrade() -> None:
|
31
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
32
|
+
with op.batch_alter_table("taskv2", schema=None) as batch_op:
|
33
|
+
batch_op.add_column(
|
34
|
+
sa.Column(
|
35
|
+
"owner", sa.VARCHAR(), autoincrement=False, nullable=True
|
36
|
+
)
|
37
|
+
)
|
38
|
+
batch_op.alter_column(
|
39
|
+
"taskgroupv2_id", existing_type=sa.INTEGER(), nullable=True
|
40
|
+
)
|
41
|
+
|
42
|
+
# ### end Alembic commands ###
|
@@ -73,6 +73,11 @@ async def _pip_install(
|
|
73
73
|
),
|
74
74
|
logger_name=logger_name,
|
75
75
|
)
|
76
|
+
await execute_command(
|
77
|
+
cwd=Path(task_group.venv_path),
|
78
|
+
command=f"{python_bin} -m pip install setuptools",
|
79
|
+
logger_name=logger_name,
|
80
|
+
)
|
76
81
|
await execute_command(
|
77
82
|
cwd=Path(task_group.venv_path),
|
78
83
|
command=f"{python_bin} -m pip install {pip_install_str}",
|
@@ -205,7 +205,7 @@ def background_collect_pip_ssh(
|
|
205
205
|
remove_venv_folder_upon_failure = True
|
206
206
|
|
207
207
|
stdout = _customize_and_run_template(
|
208
|
-
script_filename="
|
208
|
+
script_filename="_2_preliminary_pip_operations.sh",
|
209
209
|
**common_args,
|
210
210
|
)
|
211
211
|
stdout = _customize_and_run_template(
|
@@ -1,35 +1,29 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fractal-server
|
3
|
-
Version: 2.7.
|
3
|
+
Version: 2.7.1
|
4
4
|
Summary: Server component of the Fractal analytics platform
|
5
5
|
Home-page: https://github.com/fractal-analytics-platform/fractal-server
|
6
6
|
License: BSD-3-Clause
|
7
7
|
Author: Tommaso Comparin
|
8
8
|
Author-email: tommaso.comparin@exact-lab.it
|
9
|
-
Requires-Python: >=3.
|
9
|
+
Requires-Python: >=3.10,<4.0
|
10
10
|
Classifier: License :: OSI Approved :: BSD License
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
15
14
|
Classifier: Programming Language :: Python :: 3.12
|
16
|
-
Provides-Extra: gunicorn
|
17
|
-
Provides-Extra: postgres
|
18
|
-
Provides-Extra: postgres-psycopg-binary
|
19
15
|
Requires-Dist: aiosqlite (>=0.19.0,<0.20.0)
|
20
16
|
Requires-Dist: alembic (>=1.13.1,<2.0.0)
|
21
|
-
Requires-Dist: asyncpg (>=0.29.0,<0.30.0) ; extra == "postgres"
|
22
17
|
Requires-Dist: bcrypt (==4.0.1)
|
23
18
|
Requires-Dist: cloudpickle (>=3.0.0,<3.1.0)
|
24
19
|
Requires-Dist: clusterfutures (>=0.5,<0.6)
|
25
20
|
Requires-Dist: fabric (>=3.2.2,<4.0.0)
|
26
21
|
Requires-Dist: fastapi (>=0.115.0,<0.116.0)
|
27
22
|
Requires-Dist: fastapi-users[oauth] (>=12.1.0,<13.0.0)
|
28
|
-
Requires-Dist: gunicorn (>=21.2,<23.0)
|
23
|
+
Requires-Dist: gunicorn (>=21.2,<23.0)
|
29
24
|
Requires-Dist: packaging (>=23.2,<24.0)
|
30
25
|
Requires-Dist: psutil (>=5.9.8,<6.0.0)
|
31
|
-
Requires-Dist:
|
32
|
-
Requires-Dist: psycopg[binary] (>=3.1.0,<4.0.0) ; extra == "postgres-psycopg-binary"
|
26
|
+
Requires-Dist: psycopg[binary] (>=3.1.0,<4.0.0)
|
33
27
|
Requires-Dist: pydantic (>=1.10.8,<2)
|
34
28
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
35
29
|
Requires-Dist: sqlalchemy[asyncio] (>=2.0.23,<2.1)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
fractal_server/__init__.py,sha256=
|
1
|
+
fractal_server/__init__.py,sha256=e5_mOoPeW4Rbnylz3g2qQviWIKwVN-scjNbLROol4Xc,22
|
2
2
|
fractal_server/__main__.py,sha256=dEkCfzLLQrIlxsGC-HBfoR-RBMWnJDgNrxYTyzmE9c0,6146
|
3
3
|
fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
|
4
4
|
fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -20,7 +20,7 @@ fractal_server/app/models/v2/collection_state.py,sha256=Yx18ZbywjraOdlHFyRVlb3VP
|
|
20
20
|
fractal_server/app/models/v2/dataset.py,sha256=-7sxHEw4IIAvF_uSan7tA3o8hvoakBkQ0SRvqS2iOQU,1455
|
21
21
|
fractal_server/app/models/v2/job.py,sha256=ypJmN-qspkKBGhBG7Mt-HypSQqcQ2EmB4Bzzb2-y550,1535
|
22
22
|
fractal_server/app/models/v2/project.py,sha256=rAHoh5KfYwIaW7rTX0_O0jvWmxEvfo1BafvmcXuSSRk,786
|
23
|
-
fractal_server/app/models/v2/task.py,sha256=
|
23
|
+
fractal_server/app/models/v2/task.py,sha256=YIqPCQY4y0DbgWpDxYkZNApElFoHMEz_9GIP1nEGvUI,3295
|
24
24
|
fractal_server/app/models/v2/workflow.py,sha256=YBgFGCziUgU0aJ5EM3Svu9W2c46AewZO9VBlFCHiSps,1069
|
25
25
|
fractal_server/app/models/v2/workflowtask.py,sha256=iDuJYk8kp4PNqGmbKRtGI7y-QsbjkNd_gDsbMzL4i-g,1274
|
26
26
|
fractal_server/app/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -30,7 +30,7 @@ fractal_server/app/routes/admin/v2/__init__.py,sha256=zkdrk3mSQWfgTJugS8Sc_Q_xCA
|
|
30
30
|
fractal_server/app/routes/admin/v2/job.py,sha256=JmNmF5MeHtcDQzGadCJWwFNDQvB5G8SwVABaL11S1Vc,8268
|
31
31
|
fractal_server/app/routes/admin/v2/project.py,sha256=luy-yiGX1JYTdPm1hpIdDUUqPm8xHuipLy9k2X6zu74,1223
|
32
32
|
fractal_server/app/routes/admin/v2/task.py,sha256=Y0eujBgGhVapNXfW9azDxw4EBzLmEmCdh70y1RNQcb0,3895
|
33
|
-
fractal_server/app/routes/admin/v2/task_group.py,sha256=
|
33
|
+
fractal_server/app/routes/admin/v2/task_group.py,sha256=KAp7QJ6AmrNLnYb8nXEEZj5AN8MqTUM6G2PpWEYKL7s,5726
|
34
34
|
fractal_server/app/routes/api/__init__.py,sha256=2IDheFi0OFdsUg7nbUiyahqybvpgXqeHUXIL2QtWrQQ,641
|
35
35
|
fractal_server/app/routes/api/v1/__init__.py,sha256=Y2HQdG197J0a7DyQEE2jn53IfxD0EHGhzK1I2JZuEck,958
|
36
36
|
fractal_server/app/routes/api/v1/_aux_functions.py,sha256=P9Q48thGH95w0h5cacYoibxqgiiLW4oqZ8rNJ2LIISY,13219
|
@@ -50,12 +50,12 @@ fractal_server/app/routes/api/v2/job.py,sha256=Bga2Kz1OjvDIdxZObWaaXVhNIhC_5JKhK
|
|
50
50
|
fractal_server/app/routes/api/v2/project.py,sha256=eWYFJ7F2ZYQcpi-_n-rhPF-Q4gJhzYBsVGYFhHZZXAE,6653
|
51
51
|
fractal_server/app/routes/api/v2/status.py,sha256=6N9DSZ4iFqbZImorWfEAPoyoFUgEruo4Hweqo0x0xXU,6435
|
52
52
|
fractal_server/app/routes/api/v2/submit.py,sha256=tq-NGnUlpIcm_MRN47rJRHkRcIJ5HiL4Wj1wItJy3o8,8185
|
53
|
-
fractal_server/app/routes/api/v2/task.py,sha256=
|
54
|
-
fractal_server/app/routes/api/v2/task_collection.py,sha256=
|
55
|
-
fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=
|
53
|
+
fractal_server/app/routes/api/v2/task.py,sha256=hrYuVT6XDb3eFFUkQJrAf0O9eMDlE4bi0v5iJyQ5J5E,7293
|
54
|
+
fractal_server/app/routes/api/v2/task_collection.py,sha256=URtHWrUy83v17uJyIciILYPuZXbGRryw-xO2FC5J9PQ,11581
|
55
|
+
fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=kWctxgNi20uwsfHx9l_mwhI50OIxWRESjU9UiCDVVFU,6217
|
56
56
|
fractal_server/app/routes/api/v2/task_group.py,sha256=P32EUYbtGThexSWe5zI9WUFrgoOMof035fJBILTNnfQ,5580
|
57
57
|
fractal_server/app/routes/api/v2/workflow.py,sha256=PyvkrUHHzFGUGZE5X0VW5u3DPQA7wtXXNcEpG7-N66I,8687
|
58
|
-
fractal_server/app/routes/api/v2/workflow_import.py,sha256=
|
58
|
+
fractal_server/app/routes/api/v2/workflow_import.py,sha256=rD26vZ-ztjehvglrERixTeHtXuzepAtgAuPiKRNz84Q,10981
|
59
59
|
fractal_server/app/routes/api/v2/workflowtask.py,sha256=ciHTwXXFiFnMF7ZpJ3Xs0q6YfuZrFvIjqndlzAEdZpo,6969
|
60
60
|
fractal_server/app/routes/auth/__init__.py,sha256=fao6CS0WiAjHDTvBzgBVV_bSXFpEAeDBF6Z6q7rRkPc,1658
|
61
61
|
fractal_server/app/routes/auth/_aux_auth.py,sha256=ifkNocTYatBSMYGwiR14qohmvR9SfMldceiEj6uJBrU,4783
|
@@ -84,7 +84,7 @@ fractal_server/app/runner/executors/slurm/remote.py,sha256=wLziIsGdSMiO-jIXM8x77
|
|
84
84
|
fractal_server/app/runner/executors/slurm/ssh/__init__.py,sha256=Cjn1rYvljddi96tAwS-qqGkNfOcfPzjChdaEZEObCcM,65
|
85
85
|
fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py,sha256=bKo5Ja0IGxJWpPWyh9dN0AG-PwzTDZzD5LyaEHB3YU4,3742
|
86
86
|
fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py,sha256=rwlqZzoGo4SAb4nSlFjsQJdaCgfM1J6YGcjb8yYxlqc,4506
|
87
|
-
fractal_server/app/runner/executors/slurm/ssh/executor.py,sha256=
|
87
|
+
fractal_server/app/runner/executors/slurm/ssh/executor.py,sha256=si_RHAMnXwQorQ_gWeZ_hQ_cNQbbAuYPjg7nwFQoPVg,58709
|
88
88
|
fractal_server/app/runner/executors/slurm/sudo/__init__.py,sha256=Cjn1rYvljddi96tAwS-qqGkNfOcfPzjChdaEZEObCcM,65
|
89
89
|
fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py,sha256=wAgwpVcr6JIslKHOuS0FhRa_6T1KCManyRJqA-fifzw,1909
|
90
90
|
fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py,sha256=z5LlhaiqAb8pHsF1WwdzXN39C5anQmwjo1rSQgtRAYE,4422
|
@@ -145,7 +145,7 @@ fractal_server/app/schemas/v1/state.py,sha256=GYeOE_1PtDOgu5W4t_3gw3DBHXH2aCGzIN
|
|
145
145
|
fractal_server/app/schemas/v1/task.py,sha256=7BxOZ_qoRQ8n3YbQpDvB7VMcxB5fSYQmR5RLIWhuJ5U,3704
|
146
146
|
fractal_server/app/schemas/v1/task_collection.py,sha256=uvq9bcMaGD_qHsh7YtcpoSAkVAbw12eY4DocIO3MKOg,3057
|
147
147
|
fractal_server/app/schemas/v1/workflow.py,sha256=tuOs5E5Q_ozA8if7YPZ07cQjzqB_QMkBS4u92qo4Ro0,4618
|
148
|
-
fractal_server/app/schemas/v2/__init__.py,sha256=
|
148
|
+
fractal_server/app/schemas/v2/__init__.py,sha256=97y6QY0I4322CPXQCt3WO3QBWhVkmFgwLn8y2ZwWNR0,2382
|
149
149
|
fractal_server/app/schemas/v2/dataset.py,sha256=865ia13E9mWu1DaYyppKW2csNYglaInrScrprdVYX7A,2552
|
150
150
|
fractal_server/app/schemas/v2/dumps.py,sha256=s6dg-pHZFui6t2Ktm0SMxjKDN-v-ZqBHz9iTsBQF3eU,1712
|
151
151
|
fractal_server/app/schemas/v2/job.py,sha256=oYSLYkQ0HL83QyjEGIaggtZ117FndzFlONMKWd9sTXM,3270
|
@@ -154,13 +154,12 @@ fractal_server/app/schemas/v2/project.py,sha256=UXEA0UUUe0bFFOVLLmVtvDFLBO5vmD1J
|
|
154
154
|
fractal_server/app/schemas/v2/status.py,sha256=SQaUpQkjFq5c5k5J4rOjNhuQaDOEg8lksPhkKmPU5VU,332
|
155
155
|
fractal_server/app/schemas/v2/task.py,sha256=FFAbYwDlqowB8gVMdjFVPVHvAM0T89PYLixUth49xfQ,6870
|
156
156
|
fractal_server/app/schemas/v2/task_collection.py,sha256=Ddw_7QaQ93kdEIwWQvzLQDu03gho_OHdhah3n0ioK3M,6296
|
157
|
-
fractal_server/app/schemas/v2/task_group.py,sha256=
|
157
|
+
fractal_server/app/schemas/v2/task_group.py,sha256=oWy7NNsw8Co85qpLyK8FPNTgpAMvx0ZuXjIOVZuLEpM,2466
|
158
158
|
fractal_server/app/schemas/v2/workflow.py,sha256=HSNQSrBRdoBzh8Igr76FUWCAWvVzykrqmUv1vGv-8og,2026
|
159
159
|
fractal_server/app/schemas/v2/workflowtask.py,sha256=vDdMktYbHeYBgB5OuWSv6wRPRXWqvetkeqQ7IC5YtfA,5751
|
160
|
-
fractal_server/app/security/__init__.py,sha256=
|
160
|
+
fractal_server/app/security/__init__.py,sha256=8Xd4GxumZgvxEH1Vli3ULehwdesEPiaAbtffJvAEgNo,12509
|
161
161
|
fractal_server/app/user_settings.py,sha256=aZgQ3i0JkHfgwLGW1ee6Gzr1ae3IioFfJKKSsSS8Svk,1312
|
162
|
-
fractal_server/config.py,sha256
|
163
|
-
fractal_server/data_migrations/2_7_0.py,sha256=DQQJ_tLYFteH3Jw246ovIh3Dac_9SaAefoy7FLw5Cso,11145
|
162
|
+
fractal_server/config.py,sha256=-G1RvmaeSb6_wffUFuaAmhJV3u1q3HRpMLEfpGXBrz4,22797
|
164
163
|
fractal_server/data_migrations/README.md,sha256=_3AEFvDg9YkybDqCLlFPdDmGJvr6Tw7HRI14aZ3LOIw,398
|
165
164
|
fractal_server/data_migrations/tools.py,sha256=LeMeASwYGtEqd-3wOLle6WARdTGAimoyMmRbbJl-hAM,572
|
166
165
|
fractal_server/gunicorn_fractal.py,sha256=u6U01TLGlXgq1v8QmEpLih3QnsInZD7CqphgJ_GrGzc,1230
|
@@ -183,6 +182,7 @@ fractal_server/migrations/versions/5bf02391cfef_v2.py,sha256=axhNkr_H6R4rRbY7oGY
|
|
183
182
|
fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py,sha256=Q-DsMzG3IcUV2Ol1dhJWosDvKERamBE6QvA2zzS5zpQ,1632
|
184
183
|
fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py,sha256=mbWuCkTpRAdGbRhW7lhXs_e5S6O37UAcCN6JfoY5H8A,1353
|
185
184
|
fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py,sha256=NSCuhANChsg76vBkShBl-9tQ4VEHubOjtAv1etHhlvY,2684
|
185
|
+
fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py,sha256=68y9-fpSuKx6KPtM_9n8Ho0I1qwa8IoG-yJqXUYQrGg,1111
|
186
186
|
fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py,sha256=6pgODDtyAxevZvAJBj9IJ41inhV1RpwbpZr_qfPPu1A,1115
|
187
187
|
fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py,sha256=yL3-Hvzw5jBLKj4LFP1z5ofZE9L9W3tLwYtPNW7z4ko,1508
|
188
188
|
fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py,sha256=eKTZm3EgUgapXBxO0RuHkEfTKic-TZG3ADaMpGLuc0k,1057
|
@@ -209,13 +209,13 @@ fractal_server/tasks/v1/endpoint_operations.py,sha256=YyMU1Y3Xt7D9WOKqaMLuwEoIaA
|
|
209
209
|
fractal_server/tasks/v1/get_collection_data.py,sha256=5C22jp356rCH5IIC0J57wOu-DCC_kp3B6p68JooN7IM,508
|
210
210
|
fractal_server/tasks/v1/utils.py,sha256=J9oKys-82OehBxOon5wWl3CxjVBgYWeVEEyWGVFnreI,1759
|
211
211
|
fractal_server/tasks/v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
212
|
-
fractal_server/tasks/v2/_venv_pip.py,sha256=
|
212
|
+
fractal_server/tasks/v2/_venv_pip.py,sha256=EaauUkFIk4wa79B4OudhV8CYOzgWftTdfz8u4bEdjgo,6555
|
213
213
|
fractal_server/tasks/v2/background_operations.py,sha256=jWmg_XkBmcXQfPKJu_eu0wtDL4sMp1QP2bIZ9QtMj_Y,15411
|
214
|
-
fractal_server/tasks/v2/background_operations_ssh.py,sha256=
|
214
|
+
fractal_server/tasks/v2/background_operations_ssh.py,sha256=_G0rOohkkHLGcvCQyfoMObnII-sxVwrLDW2PKz8IfCQ,13631
|
215
215
|
fractal_server/tasks/v2/database_operations.py,sha256=6r56yyFPnEBrXl6ncmO6D76znzISQCFZqCYcD-Ummd4,1213
|
216
216
|
fractal_server/tasks/v2/endpoint_operations.py,sha256=MtUoI0XWHuPSousDeH2IC2WU--AUKQVup6Q6AbHiNUA,4102
|
217
217
|
fractal_server/tasks/v2/templates/_1_create_venv.sh,sha256=7tt-B6n8KRN-pannZ0enE6XSxyq-hKRYRGY63CvtINI,1151
|
218
|
-
fractal_server/tasks/v2/templates/
|
218
|
+
fractal_server/tasks/v2/templates/_2_preliminary_pip_operations.sh,sha256=NwkfDtNeT4YjUsqZuK4uN71bTe-wHIn2wmNhflimRo8,595
|
219
219
|
fractal_server/tasks/v2/templates/_3_pip_install.sh,sha256=T9sabeB9iQzVZpLfuLkKGz9EpfHkUrJHKWO4HNij6yM,595
|
220
220
|
fractal_server/tasks/v2/templates/_4_pip_freeze.sh,sha256=qHdDKu1svXi1VQKGePciEJK4_uEKuwAvwaDCcGxSvNk,274
|
221
221
|
fractal_server/tasks/v2/templates/_5_pip_show.sh,sha256=GrJ19uHYQxANEy9JaeNJZVTquY9c8Ww9eCdnC7eLVr0,1754
|
@@ -223,8 +223,8 @@ fractal_server/tasks/v2/utils.py,sha256=MnY6MhcxDRo4rPuXo2tQ252eWEPZF3OlCGe-p5Mr
|
|
223
223
|
fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
|
224
224
|
fractal_server/utils.py,sha256=jrlCBPmC7F0ptBVcDac-EbZNsdYTLbHfX9oxkXthS5Q,2193
|
225
225
|
fractal_server/zip_tools.py,sha256=xYpzBshysD2nmxkD5WLYqMzPYUcCRM3kYy-7n9bJL-U,4426
|
226
|
-
fractal_server-2.7.
|
227
|
-
fractal_server-2.7.
|
228
|
-
fractal_server-2.7.
|
229
|
-
fractal_server-2.7.
|
230
|
-
fractal_server-2.7.
|
226
|
+
fractal_server-2.7.1.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
|
227
|
+
fractal_server-2.7.1.dist-info/METADATA,sha256=L3ZYv96IVgdninD7rHRXpJ6M7HqL_zh2KMG86cqHtlE,4303
|
228
|
+
fractal_server-2.7.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
229
|
+
fractal_server-2.7.1.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
|
230
|
+
fractal_server-2.7.1.dist-info/RECORD,,
|
@@ -1,323 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
import logging
|
3
|
-
import os
|
4
|
-
import sys
|
5
|
-
from pathlib import Path
|
6
|
-
from typing import Any
|
7
|
-
from typing import Optional
|
8
|
-
|
9
|
-
from fastapi import HTTPException
|
10
|
-
from sqlalchemy import select
|
11
|
-
from sqlalchemy.orm import Session
|
12
|
-
|
13
|
-
from fractal_server.app.db import get_async_db
|
14
|
-
from fractal_server.app.db import get_sync_db
|
15
|
-
from fractal_server.app.models import TaskGroupV2
|
16
|
-
from fractal_server.app.models import TaskV2
|
17
|
-
from fractal_server.app.models import UserGroup
|
18
|
-
from fractal_server.app.models import UserOAuth
|
19
|
-
from fractal_server.app.models import UserSettings
|
20
|
-
from fractal_server.app.routes.api.v2._aux_functions_tasks import (
|
21
|
-
_verify_non_duplication_group_constraint,
|
22
|
-
)
|
23
|
-
from fractal_server.app.routes.api.v2._aux_functions_tasks import (
|
24
|
-
_verify_non_duplication_user_constraint,
|
25
|
-
)
|
26
|
-
from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
|
27
|
-
from fractal_server.data_migrations.tools import _check_current_version
|
28
|
-
from fractal_server.tasks.utils import _normalize_package_name
|
29
|
-
from fractal_server.utils import get_timestamp
|
30
|
-
|
31
|
-
logger = logging.getLogger("fix_db")
|
32
|
-
|
33
|
-
|
34
|
-
async def check_non_duplication_constraints(
|
35
|
-
*,
|
36
|
-
user_id: int,
|
37
|
-
pkg_name: str,
|
38
|
-
version: Optional[str] = None,
|
39
|
-
user_group_id: Optional[int] = None,
|
40
|
-
):
|
41
|
-
try:
|
42
|
-
async for db_async in get_async_db():
|
43
|
-
await _verify_non_duplication_user_constraint(
|
44
|
-
user_id=user_id,
|
45
|
-
pkg_name=pkg_name,
|
46
|
-
version=version,
|
47
|
-
db=db_async,
|
48
|
-
)
|
49
|
-
await _verify_non_duplication_group_constraint(
|
50
|
-
user_group_id=user_group_id,
|
51
|
-
pkg_name=pkg_name,
|
52
|
-
version=version,
|
53
|
-
db=db_async,
|
54
|
-
)
|
55
|
-
except HTTPException as e:
|
56
|
-
logger.error(
|
57
|
-
"Adding a `TaskGroupV2` with "
|
58
|
-
f"{user_id=}, {pkg_name=}, {version=} and {user_group_id=} "
|
59
|
-
"would break the non-duplication constraint."
|
60
|
-
)
|
61
|
-
logger.error(f"Original error: {str(e)}")
|
62
|
-
|
63
|
-
sys.exit("ERROR")
|
64
|
-
|
65
|
-
|
66
|
-
def get_unique_value(list_of_objects: list[dict[str, Any]], key: str):
|
67
|
-
"""
|
68
|
-
Loop over `list_of_objects` and extract (unique) value for `key`.
|
69
|
-
"""
|
70
|
-
unique_values = set()
|
71
|
-
for this_obj in list_of_objects:
|
72
|
-
this_value = this_obj.get(key, None)
|
73
|
-
unique_values.add(this_value)
|
74
|
-
if len(unique_values) != 1:
|
75
|
-
raise RuntimeError(
|
76
|
-
f"There must be a single taskgroup `{key}`, but {unique_values=}"
|
77
|
-
)
|
78
|
-
return unique_values.pop()
|
79
|
-
|
80
|
-
|
81
|
-
def get_users_mapping(db) -> dict[str, int]:
|
82
|
-
logger.warning("START _check_users")
|
83
|
-
print()
|
84
|
-
|
85
|
-
stm_users = select(UserOAuth).order_by(UserOAuth.id)
|
86
|
-
users = db.execute(stm_users).scalars().unique().all()
|
87
|
-
name_to_user_id = {}
|
88
|
-
for user in users:
|
89
|
-
logger.warning(f"START handling user {user.id}: '{user.email}'")
|
90
|
-
# Compute "name" attribute
|
91
|
-
user_settings = db.get(UserSettings, user.user_settings_id)
|
92
|
-
name = user.username or user_settings.slurm_user
|
93
|
-
logger.warning(f"{name=}")
|
94
|
-
# Fail for missing values
|
95
|
-
if name is None:
|
96
|
-
raise ValueError(
|
97
|
-
f"User with {user.id=} and {user.email=} has no "
|
98
|
-
"`username` or `slurm_user` set."
|
99
|
-
"Please fix this issue manually."
|
100
|
-
)
|
101
|
-
# Fail for non-unique values
|
102
|
-
existing_user = name_to_user_id.get(name, None)
|
103
|
-
if existing_user is not None:
|
104
|
-
raise ValueError(
|
105
|
-
f"User with {user.id=} and {user.email=} has same "
|
106
|
-
f"`(username or slurm_user)={name}` as another user. "
|
107
|
-
"Please fix this issue manually."
|
108
|
-
)
|
109
|
-
# Update dictionary
|
110
|
-
name_to_user_id[name] = user.id
|
111
|
-
logger.warning(f"END handling user {user.id}: '{user.email}'")
|
112
|
-
print()
|
113
|
-
logger.warning("END _check_users")
|
114
|
-
print()
|
115
|
-
return name_to_user_id
|
116
|
-
|
117
|
-
|
118
|
-
def get_default_user_group_id(db):
|
119
|
-
stm = select(UserGroup.id).where(
|
120
|
-
UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
|
121
|
-
)
|
122
|
-
res = db.execute(stm)
|
123
|
-
default_group_id = res.scalars().one_or_none()
|
124
|
-
if default_group_id is None:
|
125
|
-
raise RuntimeError("Default user group is missing.")
|
126
|
-
else:
|
127
|
-
return default_group_id
|
128
|
-
|
129
|
-
|
130
|
-
def get_default_user_id(db):
|
131
|
-
DEFAULT_USER_EMAIL = os.getenv("FRACTAL_V27_DEFAULT_USER_EMAIL")
|
132
|
-
if DEFAULT_USER_EMAIL is None:
|
133
|
-
raise ValueError(
|
134
|
-
"FRACTAL_V27_DEFAULT_USER_EMAIL env variable is not set. "
|
135
|
-
"Please set it to be the email of the user who will own "
|
136
|
-
"all previously-global tasks."
|
137
|
-
)
|
138
|
-
|
139
|
-
stm = select(UserOAuth.id).where(UserOAuth.email == DEFAULT_USER_EMAIL)
|
140
|
-
res = db.execute(stm)
|
141
|
-
default_user_id = res.scalars().one_or_none()
|
142
|
-
if default_user_id is None:
|
143
|
-
raise RuntimeError(
|
144
|
-
f"Default user with email {DEFAULT_USER_EMAIL} is missing."
|
145
|
-
)
|
146
|
-
else:
|
147
|
-
return default_user_id
|
148
|
-
|
149
|
-
|
150
|
-
def prepare_task_groups(
|
151
|
-
*,
|
152
|
-
user_mapping: dict[str, int],
|
153
|
-
default_user_group_id: int,
|
154
|
-
default_user_id: int,
|
155
|
-
db: Session,
|
156
|
-
):
|
157
|
-
stm_tasks = select(TaskV2).order_by(TaskV2.id)
|
158
|
-
res = db.execute(stm_tasks).scalars().all()
|
159
|
-
task_groups = {}
|
160
|
-
for task in res:
|
161
|
-
if (
|
162
|
-
task.source.startswith(("pip_remote", "pip_local"))
|
163
|
-
and task.source.count(":") == 5
|
164
|
-
):
|
165
|
-
source_fields = task.source.split(":")
|
166
|
-
(
|
167
|
-
collection_mode,
|
168
|
-
pkg_name,
|
169
|
-
version,
|
170
|
-
extras,
|
171
|
-
python_version,
|
172
|
-
name,
|
173
|
-
) = source_fields
|
174
|
-
pkg_name = _normalize_package_name(pkg_name)
|
175
|
-
task_group_key = ":".join(
|
176
|
-
[pkg_name, version, extras, python_version]
|
177
|
-
)
|
178
|
-
if collection_mode == "pip_remote":
|
179
|
-
origin = "pypi"
|
180
|
-
elif collection_mode == "pip_local":
|
181
|
-
origin = "wheel-file"
|
182
|
-
else:
|
183
|
-
raise RuntimeError(
|
184
|
-
f"Invalid {collection_mode=} for {task.source=}."
|
185
|
-
)
|
186
|
-
new_obj = dict(
|
187
|
-
task=task,
|
188
|
-
user_id=default_user_id,
|
189
|
-
origin=origin,
|
190
|
-
pkg_name=pkg_name,
|
191
|
-
version=version,
|
192
|
-
pip_extras=extras,
|
193
|
-
python_version=python_version,
|
194
|
-
)
|
195
|
-
|
196
|
-
if task_group_key in task_groups:
|
197
|
-
task_groups[task_group_key].append(new_obj)
|
198
|
-
else:
|
199
|
-
task_groups[task_group_key] = [new_obj]
|
200
|
-
else:
|
201
|
-
owner = task.owner
|
202
|
-
if owner is None:
|
203
|
-
raise RuntimeError(
|
204
|
-
"Error: `owner` is `None` for "
|
205
|
-
f"{task.id=}, {task.source=}, {task.owner=}."
|
206
|
-
)
|
207
|
-
user_id = user_mapping.get(owner, None)
|
208
|
-
if user_id is None:
|
209
|
-
raise RuntimeError(
|
210
|
-
"Error: `user_id` is `None` for "
|
211
|
-
f"{task.id=}, {task.source=}, {task.owner=}"
|
212
|
-
)
|
213
|
-
task_group_key = "-".join(
|
214
|
-
[
|
215
|
-
"NOT_PIP",
|
216
|
-
str(task.id),
|
217
|
-
str(task.version),
|
218
|
-
task.source,
|
219
|
-
str(task.owner),
|
220
|
-
]
|
221
|
-
)
|
222
|
-
if task_group_key in task_groups:
|
223
|
-
raise RuntimeError(
|
224
|
-
f"ERROR: Duplicated {task_group_key=} for "
|
225
|
-
f"{task.id=}, {task.source=}, {task.owner=}"
|
226
|
-
)
|
227
|
-
else:
|
228
|
-
task_groups[task_group_key] = [
|
229
|
-
dict(
|
230
|
-
task=task,
|
231
|
-
user_id=user_id,
|
232
|
-
origin="other",
|
233
|
-
pkg_name=task.source,
|
234
|
-
version=task.version,
|
235
|
-
)
|
236
|
-
]
|
237
|
-
|
238
|
-
for task_group_key, task_group_objects in task_groups.items():
|
239
|
-
print("-" * 80)
|
240
|
-
print(f"Start handling task group with key '{task_group_key}")
|
241
|
-
task_group_task_list = [item["task"] for item in task_group_objects]
|
242
|
-
print("List of tasks to be included")
|
243
|
-
for task in task_group_task_list:
|
244
|
-
print(f" {task.id=}, {task.source=}")
|
245
|
-
|
246
|
-
task_group_attributes = dict(
|
247
|
-
pkg_name=get_unique_value(task_group_objects, "pkg_name"),
|
248
|
-
version=get_unique_value(task_group_objects, "version"),
|
249
|
-
origin=get_unique_value(task_group_objects, "origin"),
|
250
|
-
user_id=get_unique_value(task_group_objects, "user_id"),
|
251
|
-
user_group_id=default_user_group_id,
|
252
|
-
python_version=get_unique_value(
|
253
|
-
task_group_objects, "python_version"
|
254
|
-
),
|
255
|
-
pip_extras=get_unique_value(task_group_objects, "pip_extras"),
|
256
|
-
task_list=task_group_task_list,
|
257
|
-
active=True,
|
258
|
-
timestamp_created=get_timestamp(),
|
259
|
-
)
|
260
|
-
|
261
|
-
if not task_group_key.startswith("NOT_PIP"):
|
262
|
-
cmd = next(
|
263
|
-
getattr(task_group_task_list[0], attr_name)
|
264
|
-
for attr_name in ["command_non_parallel", "command_parallel"]
|
265
|
-
if getattr(task_group_task_list[0], attr_name) is not None
|
266
|
-
)
|
267
|
-
python_bin = cmd.split()[0]
|
268
|
-
venv_path = Path(python_bin).parents[1].as_posix()
|
269
|
-
path = Path(python_bin).parents[2].as_posix()
|
270
|
-
task_group_attributes["venv_path"] = venv_path
|
271
|
-
task_group_attributes["path"] = path
|
272
|
-
|
273
|
-
print()
|
274
|
-
print("List of task-group attributes")
|
275
|
-
for key, value in task_group_attributes.items():
|
276
|
-
if key != "task_list":
|
277
|
-
print(f" {key}: {value}")
|
278
|
-
|
279
|
-
print()
|
280
|
-
|
281
|
-
# Verify non-duplication constraints
|
282
|
-
asyncio.run(
|
283
|
-
check_non_duplication_constraints(
|
284
|
-
user_id=task_group_attributes["user_id"],
|
285
|
-
user_group_id=task_group_attributes["user_group_id"],
|
286
|
-
pkg_name=task_group_attributes["pkg_name"],
|
287
|
-
version=task_group_attributes["version"],
|
288
|
-
)
|
289
|
-
)
|
290
|
-
logger.warning(
|
291
|
-
"Non-duplication-constraint check is OK, "
|
292
|
-
"proceed and create TaskGroupV2."
|
293
|
-
)
|
294
|
-
|
295
|
-
# Create the TaskGroupV2 object and commit it
|
296
|
-
task_group = TaskGroupV2(**task_group_attributes)
|
297
|
-
db.add(task_group)
|
298
|
-
db.commit()
|
299
|
-
db.refresh(task_group)
|
300
|
-
logger.warning(f"Created task group {task_group.id=}")
|
301
|
-
print()
|
302
|
-
|
303
|
-
return
|
304
|
-
|
305
|
-
|
306
|
-
def fix_db():
|
307
|
-
logger.warning("START execution of fix_db function")
|
308
|
-
_check_current_version("2.7.0")
|
309
|
-
|
310
|
-
with next(get_sync_db()) as db:
|
311
|
-
user_mapping = get_users_mapping(db)
|
312
|
-
default_user_id = get_default_user_id(db)
|
313
|
-
default_user_group_id = get_default_user_group_id(db)
|
314
|
-
|
315
|
-
prepare_task_groups(
|
316
|
-
user_mapping=user_mapping,
|
317
|
-
default_user_id=default_user_id,
|
318
|
-
default_user_group_id=default_user_group_id,
|
319
|
-
db=db,
|
320
|
-
)
|
321
|
-
|
322
|
-
logger.warning("END of execution of fix_db function")
|
323
|
-
print()
|
File without changes
|
File without changes
|
File without changes
|