fractal-server 2.8.1__py3-none-any.whl → 2.9.0a0__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/__init__.py +3 -3
- fractal_server/app/models/v2/task.py +0 -72
- fractal_server/app/models/v2/task_group.py +102 -0
- fractal_server/app/routes/admin/v1.py +1 -20
- fractal_server/app/routes/admin/v2/job.py +1 -20
- fractal_server/app/routes/admin/v2/task_group.py +53 -13
- fractal_server/app/routes/api/v2/__init__.py +11 -2
- fractal_server/app/routes/api/v2/{_aux_functions_task_collection.py → _aux_functions_task_lifecycle.py} +43 -0
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +21 -14
- fractal_server/app/routes/api/v2/task_collection.py +26 -51
- fractal_server/app/routes/api/v2/task_collection_custom.py +3 -3
- fractal_server/app/routes/api/v2/task_group.py +83 -14
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +221 -0
- fractal_server/app/routes/api/v2/workflow.py +1 -1
- fractal_server/app/routes/api/v2/workflow_import.py +2 -2
- fractal_server/app/routes/aux/_timestamp.py +25 -0
- fractal_server/app/schemas/v2/__init__.py +3 -2
- fractal_server/app/schemas/v2/task_collection.py +0 -21
- fractal_server/app/schemas/v2/task_group.py +30 -6
- fractal_server/migrations/versions/3082479ac4ea_taskgroup_activity_and_venv_info_to_.py +105 -0
- fractal_server/ssh/_fabric.py +18 -0
- fractal_server/tasks/utils.py +2 -12
- fractal_server/tasks/v2/local/__init__.py +3 -0
- fractal_server/tasks/v2/local/collect.py +291 -0
- fractal_server/tasks/v2/local/deactivate.py +162 -0
- fractal_server/tasks/v2/local/reactivate.py +159 -0
- fractal_server/tasks/v2/local/utils_local.py +52 -0
- fractal_server/tasks/v2/ssh/__init__.py +0 -0
- fractal_server/tasks/v2/ssh/collect.py +387 -0
- fractal_server/tasks/v2/ssh/deactivate.py +2 -0
- fractal_server/tasks/v2/ssh/reactivate.py +2 -0
- fractal_server/tasks/v2/templates/{_2_preliminary_pip_operations.sh → 1_create_venv.sh} +6 -7
- fractal_server/tasks/v2/templates/{_3_pip_install.sh → 2_pip_install.sh} +8 -1
- fractal_server/tasks/v2/templates/{_4_pip_freeze.sh → 3_pip_freeze.sh} +0 -7
- fractal_server/tasks/v2/templates/{_5_pip_show.sh → 4_pip_show.sh} +5 -6
- fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +10 -0
- fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +35 -0
- fractal_server/tasks/v2/utils_background.py +42 -103
- fractal_server/tasks/v2/utils_templates.py +32 -2
- fractal_server/utils.py +4 -2
- {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/METADATA +2 -2
- {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/RECORD +47 -36
- fractal_server/app/models/v2/collection_state.py +0 -22
- fractal_server/tasks/v2/collection_local.py +0 -357
- fractal_server/tasks/v2/collection_ssh.py +0 -352
- fractal_server/tasks/v2/templates/_1_create_venv.sh +0 -42
- /fractal_server/tasks/v2/{database_operations.py → utils_database.py} +0 -0
- {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/LICENSE +0 -0
- {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/WHEEL +0 -0
- {fractal_server-2.8.1.dist-info → fractal_server-2.9.0a0.dist-info}/entry_points.txt +0 -0
@@ -17,22 +17,24 @@ from .....logger import set_logger
|
|
17
17
|
from .....syringe import Inject
|
18
18
|
from ....db import AsyncSession
|
19
19
|
from ....db import get_async_db
|
20
|
-
from ....models.v2 import CollectionStateV2
|
21
20
|
from ....models.v2 import TaskGroupV2
|
22
|
-
from ....schemas.v2 import CollectionStateReadV2
|
23
|
-
from ....schemas.v2 import CollectionStatusV2
|
24
21
|
from ....schemas.v2 import TaskCollectPipV2
|
22
|
+
from ....schemas.v2 import TaskGroupActivityStatusV2
|
23
|
+
from ....schemas.v2 import TaskGroupActivityV2Read
|
25
24
|
from ....schemas.v2 import TaskGroupCreateV2
|
26
25
|
from ...aux.validate_user_settings import validate_user_settings
|
27
|
-
from .
|
26
|
+
from ._aux_functions_task_lifecycle import get_package_version_from_pypi
|
28
27
|
from ._aux_functions_tasks import _get_valid_user_group_id
|
29
28
|
from ._aux_functions_tasks import _verify_non_duplication_group_constraint
|
30
29
|
from ._aux_functions_tasks import _verify_non_duplication_user_constraint
|
31
30
|
from fractal_server.app.models import UserOAuth
|
32
|
-
from fractal_server.app.
|
31
|
+
from fractal_server.app.models.v2 import TaskGroupActivityV2
|
33
32
|
from fractal_server.app.routes.auth import current_active_verified_user
|
33
|
+
from fractal_server.app.schemas.v2 import (
|
34
|
+
TaskGroupActivityActionV2,
|
35
|
+
)
|
34
36
|
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
35
|
-
from fractal_server.tasks.v2.
|
37
|
+
from fractal_server.tasks.v2.local.collect import (
|
36
38
|
collect_package_local,
|
37
39
|
)
|
38
40
|
from fractal_server.tasks.v2.utils_package_names import _parse_wheel_filename
|
@@ -48,7 +50,7 @@ logger = set_logger(__name__)
|
|
48
50
|
|
49
51
|
@router.post(
|
50
52
|
"/collect/pip/",
|
51
|
-
response_model=
|
53
|
+
response_model=TaskGroupActivityV2Read,
|
52
54
|
)
|
53
55
|
async def collect_tasks_pip(
|
54
56
|
task_collect: TaskCollectPipV2,
|
@@ -59,7 +61,7 @@ async def collect_tasks_pip(
|
|
59
61
|
user_group_id: Optional[int] = None,
|
60
62
|
user: UserOAuth = Depends(current_active_verified_user),
|
61
63
|
db: AsyncSession = Depends(get_async_db),
|
62
|
-
) ->
|
64
|
+
) -> TaskGroupActivityV2Read:
|
63
65
|
"""
|
64
66
|
Task collection endpoint
|
65
67
|
|
@@ -227,20 +229,17 @@ async def collect_tasks_pip(
|
|
227
229
|
db.expunge(task_group)
|
228
230
|
|
229
231
|
# All checks are OK, proceed with task collection
|
230
|
-
|
231
|
-
|
232
|
-
|
232
|
+
task_group_activity = TaskGroupActivityV2(
|
233
|
+
user_id=task_group.user_id,
|
234
|
+
taskgroupv2_id=task_group.id,
|
235
|
+
status=TaskGroupActivityStatusV2.PENDING,
|
236
|
+
action=TaskGroupActivityActionV2.COLLECT,
|
237
|
+
pkg_name=task_group.pkg_name,
|
233
238
|
version=task_group.version,
|
234
|
-
path=task_group.path,
|
235
|
-
venv_path=task_group.venv_path,
|
236
|
-
)
|
237
|
-
state = CollectionStateV2(
|
238
|
-
data=collection_state_data, taskgroupv2_id=task_group.id
|
239
239
|
)
|
240
|
-
db.add(
|
240
|
+
db.add(task_group_activity)
|
241
241
|
await db.commit()
|
242
|
-
await db.refresh(
|
243
|
-
|
242
|
+
await db.refresh(task_group_activity)
|
244
243
|
logger = set_logger(logger_name="collect_tasks_pip")
|
245
244
|
|
246
245
|
# END of SSH/non-SSH common part
|
@@ -248,7 +247,7 @@ async def collect_tasks_pip(
|
|
248
247
|
if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
|
249
248
|
# SSH task collection
|
250
249
|
|
251
|
-
from fractal_server.tasks.v2.
|
250
|
+
from fractal_server.tasks.v2.ssh.collect import (
|
252
251
|
collect_package_ssh,
|
253
252
|
)
|
254
253
|
|
@@ -263,8 +262,8 @@ async def collect_tasks_pip(
|
|
263
262
|
|
264
263
|
background_tasks.add_task(
|
265
264
|
collect_package_ssh,
|
266
|
-
|
267
|
-
|
265
|
+
task_group_id=task_group.id,
|
266
|
+
task_group_activity_id=task_group_activity.id,
|
268
267
|
fractal_ssh=fractal_ssh,
|
269
268
|
tasks_base_dir=user_settings.ssh_tasks_dir,
|
270
269
|
)
|
@@ -273,37 +272,13 @@ async def collect_tasks_pip(
|
|
273
272
|
# Local task collection
|
274
273
|
background_tasks.add_task(
|
275
274
|
collect_package_local,
|
276
|
-
|
277
|
-
|
275
|
+
task_group_id=task_group.id,
|
276
|
+
task_group_activity_id=task_group_activity.id,
|
278
277
|
)
|
279
278
|
logger.debug(
|
280
279
|
"Task-collection endpoint: start background collection "
|
281
|
-
"and return
|
280
|
+
"and return task_group_activity"
|
282
281
|
)
|
283
282
|
reset_logger_handlers(logger)
|
284
|
-
|
285
|
-
|
286
|
-
f"GET /task/collect/{state.id}/ to query collection status"
|
287
|
-
)
|
288
|
-
state.data["info"] = info
|
289
|
-
response.status_code = status.HTTP_201_CREATED
|
290
|
-
|
291
|
-
return state
|
292
|
-
|
293
|
-
|
294
|
-
@router.get("/collect/{state_id}/", response_model=CollectionStateReadV2)
|
295
|
-
async def check_collection_status(
|
296
|
-
state_id: int,
|
297
|
-
user: UserOAuth = Depends(current_active_user),
|
298
|
-
db: AsyncSession = Depends(get_async_db),
|
299
|
-
) -> CollectionStateReadV2: # State[TaskCollectStatus]
|
300
|
-
"""
|
301
|
-
Check status of background task collection
|
302
|
-
"""
|
303
|
-
state = await db.get(CollectionStateV2, state_id)
|
304
|
-
if state is None:
|
305
|
-
raise HTTPException(
|
306
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
307
|
-
detail=f"No task collection info with id={state_id}",
|
308
|
-
)
|
309
|
-
return state
|
283
|
+
response.status_code = status.HTTP_202_ACCEPTED
|
284
|
+
return task_group_activity
|
@@ -27,12 +27,12 @@ from fractal_server.config import get_settings
|
|
27
27
|
from fractal_server.logger import set_logger
|
28
28
|
from fractal_server.string_tools import validate_cmd
|
29
29
|
from fractal_server.syringe import Inject
|
30
|
-
from fractal_server.tasks.v2.database_operations import (
|
31
|
-
create_db_tasks_and_update_task_group,
|
32
|
-
)
|
33
30
|
from fractal_server.tasks.v2.utils_background import (
|
34
31
|
_prepare_tasks_metadata,
|
35
32
|
)
|
33
|
+
from fractal_server.tasks.v2.utils_database import (
|
34
|
+
create_db_tasks_and_update_task_group,
|
35
|
+
)
|
36
36
|
|
37
37
|
router = APIRouter()
|
38
38
|
|
@@ -1,3 +1,6 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import Optional
|
3
|
+
|
1
4
|
from fastapi import APIRouter
|
2
5
|
from fastapi import Depends
|
3
6
|
from fastapi import HTTPException
|
@@ -13,13 +16,16 @@ from fractal_server.app.db import AsyncSession
|
|
13
16
|
from fractal_server.app.db import get_async_db
|
14
17
|
from fractal_server.app.models import LinkUserGroup
|
15
18
|
from fractal_server.app.models import UserOAuth
|
16
|
-
from fractal_server.app.models.v2 import
|
19
|
+
from fractal_server.app.models.v2 import TaskGroupActivityV2
|
17
20
|
from fractal_server.app.models.v2 import TaskGroupV2
|
18
21
|
from fractal_server.app.models.v2 import WorkflowTaskV2
|
19
22
|
from fractal_server.app.routes.auth import current_active_user
|
20
23
|
from fractal_server.app.routes.auth._aux_auth import (
|
21
24
|
_verify_user_belongs_to_group,
|
22
25
|
)
|
26
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
|
27
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
|
28
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
|
23
29
|
from fractal_server.app.schemas.v2 import TaskGroupReadV2
|
24
30
|
from fractal_server.app.schemas.v2 import TaskGroupUpdateV2
|
25
31
|
from fractal_server.logger import set_logger
|
@@ -29,6 +35,70 @@ router = APIRouter()
|
|
29
35
|
logger = set_logger(__name__)
|
30
36
|
|
31
37
|
|
38
|
+
@router.get("/activity/", response_model=list[TaskGroupActivityV2Read])
|
39
|
+
async def get_task_group_activity_list(
|
40
|
+
task_group_activity_id: Optional[int] = None,
|
41
|
+
taskgroupv2_id: Optional[int] = None,
|
42
|
+
pkg_name: Optional[str] = None,
|
43
|
+
status: Optional[TaskGroupActivityStatusV2] = None,
|
44
|
+
action: Optional[TaskGroupActivityActionV2] = None,
|
45
|
+
timestamp_started_min: Optional[datetime] = None,
|
46
|
+
user: UserOAuth = Depends(current_active_user),
|
47
|
+
db: AsyncSession = Depends(get_async_db),
|
48
|
+
) -> list[TaskGroupActivityV2Read]:
|
49
|
+
|
50
|
+
stm = select(TaskGroupActivityV2).where(
|
51
|
+
TaskGroupActivityV2.user_id == user.id
|
52
|
+
)
|
53
|
+
if task_group_activity_id is not None:
|
54
|
+
stm = stm.where(TaskGroupActivityV2.id == task_group_activity_id)
|
55
|
+
if taskgroupv2_id is not None:
|
56
|
+
stm = stm.where(TaskGroupActivityV2.taskgroupv2_id == taskgroupv2_id)
|
57
|
+
if pkg_name is not None:
|
58
|
+
stm = stm.where(TaskGroupActivityV2.pkg_name.icontains(pkg_name))
|
59
|
+
if status is not None:
|
60
|
+
stm = stm.where(TaskGroupActivityV2.status == status)
|
61
|
+
if action is not None:
|
62
|
+
stm = stm.where(TaskGroupActivityV2.action == action)
|
63
|
+
if timestamp_started_min is not None:
|
64
|
+
stm = stm.where(
|
65
|
+
TaskGroupActivityV2.timestamp_started >= timestamp_started_min
|
66
|
+
)
|
67
|
+
|
68
|
+
res = await db.execute(stm)
|
69
|
+
activities = res.scalars().all()
|
70
|
+
return activities
|
71
|
+
|
72
|
+
|
73
|
+
@router.get(
|
74
|
+
"/activity/{task_group_activity_id}/",
|
75
|
+
response_model=TaskGroupActivityV2Read,
|
76
|
+
)
|
77
|
+
async def get_task_group_activity(
|
78
|
+
task_group_activity_id: int,
|
79
|
+
user: UserOAuth = Depends(current_active_user),
|
80
|
+
db: AsyncSession = Depends(get_async_db),
|
81
|
+
) -> TaskGroupActivityV2Read:
|
82
|
+
|
83
|
+
activity = await db.get(TaskGroupActivityV2, task_group_activity_id)
|
84
|
+
|
85
|
+
if activity is None:
|
86
|
+
raise HTTPException(
|
87
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
88
|
+
detail=f"TaskGroupActivityV2 {task_group_activity_id} not found",
|
89
|
+
)
|
90
|
+
if activity.user_id != user.id:
|
91
|
+
raise HTTPException(
|
92
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
93
|
+
detail=(
|
94
|
+
"You are not the owner of TaskGroupActivityV2 "
|
95
|
+
f"{task_group_activity_id}",
|
96
|
+
),
|
97
|
+
)
|
98
|
+
|
99
|
+
return activity
|
100
|
+
|
101
|
+
|
32
102
|
@router.get("/", response_model=list[TaskGroupReadV2])
|
33
103
|
async def get_task_group_list(
|
34
104
|
user: UserOAuth = Depends(current_active_user),
|
@@ -40,7 +110,6 @@ async def get_task_group_list(
|
|
40
110
|
"""
|
41
111
|
Get all accessible TaskGroups
|
42
112
|
"""
|
43
|
-
|
44
113
|
if only_owner:
|
45
114
|
condition = TaskGroupV2.user_id == user.id
|
46
115
|
else:
|
@@ -112,22 +181,22 @@ async def delete_task_group(
|
|
112
181
|
detail=f"TaskV2 {workflow_tasks[0].task_id} is still in use",
|
113
182
|
)
|
114
183
|
|
115
|
-
# Cascade operations: set foreign-keys to null for
|
116
|
-
# are in relationship with the current TaskGroupV2
|
117
|
-
logger.debug("Start of cascade operations on
|
118
|
-
stm = select(
|
119
|
-
|
184
|
+
# Cascade operations: set foreign-keys to null for TaskGroupActivityV2
|
185
|
+
# which are in relationship with the current TaskGroupV2
|
186
|
+
logger.debug("Start of cascade operations on TaskGroupActivityV2.")
|
187
|
+
stm = select(TaskGroupActivityV2).where(
|
188
|
+
TaskGroupActivityV2.taskgroupv2_id == task_group_id
|
120
189
|
)
|
121
190
|
res = await db.execute(stm)
|
122
|
-
|
123
|
-
for
|
191
|
+
task_group_activity_list = res.scalars().all()
|
192
|
+
for task_group_activity in task_group_activity_list:
|
124
193
|
logger.debug(
|
125
|
-
f"Setting
|
126
|
-
"to None."
|
194
|
+
f"Setting TaskGroupActivityV2[{task_group_activity.id}]"
|
195
|
+
".taskgroupv2_id to None."
|
127
196
|
)
|
128
|
-
|
129
|
-
db.add(
|
130
|
-
logger.debug("End of cascade operations on
|
197
|
+
task_group_activity.taskgroupv2_id = None
|
198
|
+
db.add(task_group_activity)
|
199
|
+
logger.debug("End of cascade operations on TaskGroupActivityV2.")
|
131
200
|
|
132
201
|
await db.delete(task_group)
|
133
202
|
await db.commit()
|
@@ -0,0 +1,221 @@
|
|
1
|
+
from fastapi import APIRouter
|
2
|
+
from fastapi import BackgroundTasks
|
3
|
+
from fastapi import Depends
|
4
|
+
from fastapi import HTTPException
|
5
|
+
from fastapi import Response
|
6
|
+
from fastapi import status
|
7
|
+
|
8
|
+
from ._aux_functions_task_lifecycle import check_no_ongoing_activity
|
9
|
+
from ._aux_functions_tasks import _get_task_group_full_access
|
10
|
+
from fractal_server.app.db import AsyncSession
|
11
|
+
from fractal_server.app.db import get_async_db
|
12
|
+
from fractal_server.app.models import UserOAuth
|
13
|
+
from fractal_server.app.models.v2 import TaskGroupActivityV2
|
14
|
+
from fractal_server.app.routes.auth import current_active_user
|
15
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
|
16
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
|
17
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
|
18
|
+
from fractal_server.app.schemas.v2 import TaskGroupReadV2
|
19
|
+
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
20
|
+
from fractal_server.config import get_settings
|
21
|
+
from fractal_server.logger import set_logger
|
22
|
+
from fractal_server.syringe import Inject
|
23
|
+
from fractal_server.tasks.v2.local import deactivate_local
|
24
|
+
from fractal_server.tasks.v2.local import reactivate_local
|
25
|
+
from fractal_server.utils import get_timestamp
|
26
|
+
|
27
|
+
router = APIRouter()
|
28
|
+
|
29
|
+
|
30
|
+
logger = set_logger(__name__)
|
31
|
+
|
32
|
+
|
33
|
+
@router.post(
|
34
|
+
"/{task_group_id}/deactivate/",
|
35
|
+
response_model=TaskGroupActivityV2Read,
|
36
|
+
)
|
37
|
+
async def deactivate_task_group(
|
38
|
+
task_group_id: int,
|
39
|
+
background_tasks: BackgroundTasks,
|
40
|
+
response: Response,
|
41
|
+
user: UserOAuth = Depends(current_active_user),
|
42
|
+
db: AsyncSession = Depends(get_async_db),
|
43
|
+
) -> TaskGroupReadV2:
|
44
|
+
"""
|
45
|
+
Deactivate task-group venv
|
46
|
+
"""
|
47
|
+
# Check access
|
48
|
+
task_group = await _get_task_group_full_access(
|
49
|
+
task_group_id=task_group_id,
|
50
|
+
user_id=user.id,
|
51
|
+
db=db,
|
52
|
+
)
|
53
|
+
|
54
|
+
# Check no other activity is ongoing
|
55
|
+
await check_no_ongoing_activity(task_group_id=task_group_id, db=db)
|
56
|
+
|
57
|
+
# Check that task-group is active
|
58
|
+
if not task_group.active:
|
59
|
+
raise HTTPException(
|
60
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
61
|
+
detail=(
|
62
|
+
f"Cannot deactivate a task group with {task_group.active=}."
|
63
|
+
),
|
64
|
+
)
|
65
|
+
|
66
|
+
# Shortcut for task-group with origin="other"
|
67
|
+
if task_group.origin == TaskGroupV2OriginEnum.OTHER:
|
68
|
+
task_group.active = False
|
69
|
+
task_group_activity = TaskGroupActivityV2(
|
70
|
+
user_id=task_group.user_id,
|
71
|
+
taskgroupv2_id=task_group.id,
|
72
|
+
status=TaskGroupActivityStatusV2.OK,
|
73
|
+
action=TaskGroupActivityActionV2.DEACTIVATE,
|
74
|
+
pkg_name=task_group.pkg_name,
|
75
|
+
version=(task_group.version or "N/A"),
|
76
|
+
log=(
|
77
|
+
f"Task group has {task_group.origin=}, set "
|
78
|
+
"task_group.active to False and exit."
|
79
|
+
),
|
80
|
+
timestamp_started=get_timestamp(),
|
81
|
+
timestamp_ended=get_timestamp(),
|
82
|
+
)
|
83
|
+
db.add(task_group)
|
84
|
+
db.add(task_group_activity)
|
85
|
+
await db.commit()
|
86
|
+
response.status_code = status.HTTP_202_ACCEPTED
|
87
|
+
return task_group_activity
|
88
|
+
|
89
|
+
task_group_activity = TaskGroupActivityV2(
|
90
|
+
user_id=task_group.user_id,
|
91
|
+
taskgroupv2_id=task_group.id,
|
92
|
+
status=TaskGroupActivityStatusV2.PENDING,
|
93
|
+
action=TaskGroupActivityActionV2.DEACTIVATE,
|
94
|
+
pkg_name=task_group.pkg_name,
|
95
|
+
version=task_group.version,
|
96
|
+
timestamp_started=get_timestamp(),
|
97
|
+
)
|
98
|
+
task_group.active = False
|
99
|
+
db.add(task_group)
|
100
|
+
db.add(task_group_activity)
|
101
|
+
await db.commit()
|
102
|
+
|
103
|
+
# Submit background task
|
104
|
+
settings = Inject(get_settings)
|
105
|
+
if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
|
106
|
+
raise HTTPException(
|
107
|
+
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
108
|
+
detail="Not implemented (yet) for SSH.",
|
109
|
+
)
|
110
|
+
else:
|
111
|
+
background_tasks.add_task(
|
112
|
+
deactivate_local,
|
113
|
+
task_group_id=task_group.id,
|
114
|
+
task_group_activity_id=task_group_activity.id,
|
115
|
+
)
|
116
|
+
|
117
|
+
logger.debug(
|
118
|
+
"Task group deactivation endpoint: start deactivate "
|
119
|
+
"and return task_group_activity"
|
120
|
+
)
|
121
|
+
response.status_code = status.HTTP_202_ACCEPTED
|
122
|
+
return task_group_activity
|
123
|
+
|
124
|
+
|
125
|
+
@router.post(
|
126
|
+
"/{task_group_id}/reactivate/",
|
127
|
+
response_model=TaskGroupActivityV2Read,
|
128
|
+
)
|
129
|
+
async def reactivate_task_group(
|
130
|
+
task_group_id: int,
|
131
|
+
background_tasks: BackgroundTasks,
|
132
|
+
response: Response,
|
133
|
+
user: UserOAuth = Depends(current_active_user),
|
134
|
+
db: AsyncSession = Depends(get_async_db),
|
135
|
+
) -> TaskGroupReadV2:
|
136
|
+
"""
|
137
|
+
Deactivate task-group venv
|
138
|
+
"""
|
139
|
+
|
140
|
+
# Check access
|
141
|
+
task_group = await _get_task_group_full_access(
|
142
|
+
task_group_id=task_group_id,
|
143
|
+
user_id=user.id,
|
144
|
+
db=db,
|
145
|
+
)
|
146
|
+
|
147
|
+
# Check that task-group is not active
|
148
|
+
if task_group.active:
|
149
|
+
raise HTTPException(
|
150
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
151
|
+
detail=(
|
152
|
+
f"Cannot reactivate a task group with {task_group.active=}."
|
153
|
+
),
|
154
|
+
)
|
155
|
+
|
156
|
+
# Check no other activity is ongoing
|
157
|
+
await check_no_ongoing_activity(task_group_id=task_group_id, db=db)
|
158
|
+
|
159
|
+
# Shortcut for task-group with origin="other"
|
160
|
+
if task_group.origin == TaskGroupV2OriginEnum.OTHER:
|
161
|
+
task_group.active = True
|
162
|
+
task_group_activity = TaskGroupActivityV2(
|
163
|
+
user_id=task_group.user_id,
|
164
|
+
taskgroupv2_id=task_group.id,
|
165
|
+
status=TaskGroupActivityStatusV2.OK,
|
166
|
+
action=TaskGroupActivityActionV2.REACTIVATE,
|
167
|
+
pkg_name=task_group.pkg_name,
|
168
|
+
version=(task_group.version or "N/A"),
|
169
|
+
log=(
|
170
|
+
f"Task group has {task_group.origin=}, set "
|
171
|
+
"task_group.active to True and exit."
|
172
|
+
),
|
173
|
+
timestamp_started=get_timestamp(),
|
174
|
+
timestamp_ended=get_timestamp(),
|
175
|
+
)
|
176
|
+
db.add(task_group)
|
177
|
+
db.add(task_group_activity)
|
178
|
+
await db.commit()
|
179
|
+
response.status_code = status.HTTP_202_ACCEPTED
|
180
|
+
return task_group_activity
|
181
|
+
|
182
|
+
if task_group.pip_freeze is None:
|
183
|
+
raise HTTPException(
|
184
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
185
|
+
detail=(
|
186
|
+
"Cannot reactivate a task group with "
|
187
|
+
f"{task_group.pip_freeze=}."
|
188
|
+
),
|
189
|
+
)
|
190
|
+
|
191
|
+
task_group_activity = TaskGroupActivityV2(
|
192
|
+
user_id=task_group.user_id,
|
193
|
+
taskgroupv2_id=task_group.id,
|
194
|
+
status=TaskGroupActivityStatusV2.PENDING,
|
195
|
+
action=TaskGroupActivityActionV2.REACTIVATE,
|
196
|
+
pkg_name=task_group.pkg_name,
|
197
|
+
version=task_group.version,
|
198
|
+
timestamp_started=get_timestamp(),
|
199
|
+
)
|
200
|
+
db.add(task_group_activity)
|
201
|
+
await db.commit()
|
202
|
+
|
203
|
+
# Submit background task
|
204
|
+
settings = Inject(get_settings)
|
205
|
+
if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
|
206
|
+
raise HTTPException(
|
207
|
+
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
208
|
+
detail="Not implemented (yet) for SSH.",
|
209
|
+
)
|
210
|
+
else:
|
211
|
+
background_tasks.add_task(
|
212
|
+
reactivate_local,
|
213
|
+
task_group_id=task_group.id,
|
214
|
+
task_group_activity_id=task_group_activity.id,
|
215
|
+
)
|
216
|
+
logger.debug(
|
217
|
+
"Task group reactivation endpoint: start reactivate "
|
218
|
+
"and return task_group_activity"
|
219
|
+
)
|
220
|
+
response.status_code = status.HTTP_202_ACCEPTED
|
221
|
+
return task_group_activity
|
@@ -23,7 +23,7 @@ from ._aux_functions import _get_submitted_jobs_statement
|
|
23
23
|
from ._aux_functions import _get_workflow_check_owner
|
24
24
|
from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
|
25
25
|
from fractal_server.app.models import UserOAuth
|
26
|
-
from fractal_server.app.models.v2
|
26
|
+
from fractal_server.app.models.v2 import TaskGroupV2
|
27
27
|
from fractal_server.app.routes.auth import current_active_user
|
28
28
|
|
29
29
|
router = APIRouter()
|
@@ -21,10 +21,10 @@ from ._aux_functions import _workflow_insert_task
|
|
21
21
|
from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
|
22
22
|
from fractal_server.app.models import LinkUserGroup
|
23
23
|
from fractal_server.app.models import UserOAuth
|
24
|
-
from fractal_server.app.models.v2
|
24
|
+
from fractal_server.app.models.v2 import TaskGroupV2
|
25
25
|
from fractal_server.app.routes.auth import current_active_user
|
26
26
|
from fractal_server.app.routes.auth._aux_auth import _get_default_usergroup_id
|
27
|
-
from fractal_server.app.schemas.v2
|
27
|
+
from fractal_server.app.schemas.v2 import TaskImportV2
|
28
28
|
from fractal_server.logger import set_logger
|
29
29
|
|
30
30
|
router = APIRouter()
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from datetime import timezone
|
3
|
+
|
4
|
+
from fastapi import HTTPException
|
5
|
+
from fastapi import status
|
6
|
+
|
7
|
+
from fractal_server.config import get_settings
|
8
|
+
from fractal_server.syringe import Inject
|
9
|
+
|
10
|
+
|
11
|
+
def _convert_to_db_timestamp(dt: datetime) -> datetime:
|
12
|
+
"""
|
13
|
+
This function takes a timezone-aware datetime and converts it to UTC.
|
14
|
+
If using SQLite, it also removes the timezone information in order to make
|
15
|
+
the datetime comparable with datetimes in the database.
|
16
|
+
"""
|
17
|
+
if dt.tzinfo is None:
|
18
|
+
raise HTTPException(
|
19
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
20
|
+
detail=f"The timestamp provided has no timezone information: {dt}",
|
21
|
+
)
|
22
|
+
_dt = dt.astimezone(timezone.utc)
|
23
|
+
if Inject(get_settings).DB_ENGINE == "sqlite":
|
24
|
+
return _dt.replace(tzinfo=None)
|
25
|
+
return _dt
|
@@ -23,10 +23,11 @@ from .task import TaskImportV2 # noqa F401
|
|
23
23
|
from .task import TaskImportV2Legacy # noqa F401
|
24
24
|
from .task import TaskReadV2 # noqa F401
|
25
25
|
from .task import TaskUpdateV2 # noqa F401
|
26
|
-
from .task_collection import CollectionStateReadV2 # noqa F401
|
27
|
-
from .task_collection import CollectionStatusV2 # noqa F401
|
28
26
|
from .task_collection import TaskCollectCustomV2 # noqa F401
|
29
27
|
from .task_collection import TaskCollectPipV2 # noqa F401
|
28
|
+
from .task_group import TaskGroupActivityActionV2 # noqa F401
|
29
|
+
from .task_group import TaskGroupActivityStatusV2 # noqa F401
|
30
|
+
from .task_group import TaskGroupActivityV2Read # noqa F401
|
30
31
|
from .task_group import TaskGroupCreateV2 # noqa F401
|
31
32
|
from .task_group import TaskGroupReadV2 # noqa F401
|
32
33
|
from .task_group import TaskGroupUpdateV2 # noqa F401
|
@@ -1,7 +1,4 @@
|
|
1
|
-
from datetime import datetime
|
2
|
-
from enum import Enum
|
3
1
|
from pathlib import Path
|
4
|
-
from typing import Any
|
5
2
|
from typing import Literal
|
6
3
|
from typing import Optional
|
7
4
|
|
@@ -11,19 +8,10 @@ from pydantic import root_validator
|
|
11
8
|
from pydantic import validator
|
12
9
|
|
13
10
|
from .._validators import valstr
|
14
|
-
from fractal_server.app.schemas._validators import valutc
|
15
11
|
from fractal_server.app.schemas.v2 import ManifestV2
|
16
12
|
from fractal_server.string_tools import validate_cmd
|
17
13
|
|
18
14
|
|
19
|
-
class CollectionStatusV2(str, Enum):
|
20
|
-
PENDING = "pending"
|
21
|
-
INSTALLING = "installing"
|
22
|
-
COLLECTING = "collecting"
|
23
|
-
FAIL = "fail"
|
24
|
-
OK = "OK"
|
25
|
-
|
26
|
-
|
27
15
|
class TaskCollectPipV2(BaseModel, extra=Extra.forbid):
|
28
16
|
"""
|
29
17
|
TaskCollectPipV2 class
|
@@ -191,12 +179,3 @@ class TaskCollectCustomV2(BaseModel, extra=Extra.forbid):
|
|
191
179
|
f"Python interpreter path must be absolute: (given {value})."
|
192
180
|
)
|
193
181
|
return value
|
194
|
-
|
195
|
-
|
196
|
-
class CollectionStateReadV2(BaseModel):
|
197
|
-
|
198
|
-
id: Optional[int]
|
199
|
-
data: dict[str, Any]
|
200
|
-
timestamp: datetime
|
201
|
-
|
202
|
-
_timestamp = validator("timestamp", allow_reuse=True)(valutc("timestamp"))
|
@@ -20,6 +20,19 @@ class TaskGroupV2OriginEnum(str, Enum):
|
|
20
20
|
OTHER = "other"
|
21
21
|
|
22
22
|
|
23
|
+
class TaskGroupActivityStatusV2(str, Enum):
|
24
|
+
PENDING = "pending"
|
25
|
+
ONGOING = "ongoing"
|
26
|
+
FAILED = "failed"
|
27
|
+
OK = "OK"
|
28
|
+
|
29
|
+
|
30
|
+
class TaskGroupActivityActionV2(str, Enum):
|
31
|
+
COLLECT = "collect"
|
32
|
+
DEACTIVATE = "deactivate"
|
33
|
+
REACTIVATE = "reactivate"
|
34
|
+
|
35
|
+
|
23
36
|
class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
|
24
37
|
user_id: int
|
25
38
|
user_group_id: Optional[int] = None
|
@@ -32,6 +45,7 @@ class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
|
|
32
45
|
venv_path: Optional[str] = None
|
33
46
|
wheel_path: Optional[str] = None
|
34
47
|
pip_extras: Optional[str] = None
|
48
|
+
pip_freeze: Optional[str] = None
|
35
49
|
pinned_package_versions: dict[str, str] = Field(default_factory=dict)
|
36
50
|
|
37
51
|
# Validators
|
@@ -67,19 +81,29 @@ class TaskGroupReadV2(BaseModel):
|
|
67
81
|
path: Optional[str] = None
|
68
82
|
venv_path: Optional[str] = None
|
69
83
|
wheel_path: Optional[str] = None
|
84
|
+
pip_freeze: Optional[str] = None
|
70
85
|
pip_extras: Optional[str] = None
|
71
86
|
pinned_package_versions: dict[str, str] = Field(default_factory=dict)
|
72
87
|
|
88
|
+
venv_size_in_kB: Optional[int] = None
|
89
|
+
venv_file_number: Optional[int] = None
|
90
|
+
|
73
91
|
active: bool
|
74
92
|
timestamp_created: datetime
|
75
93
|
|
76
94
|
|
77
95
|
class TaskGroupUpdateV2(BaseModel, extra=Extra.forbid):
|
78
96
|
user_group_id: Optional[int] = None
|
79
|
-
active: Optional[bool] = None
|
80
97
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
98
|
+
|
99
|
+
class TaskGroupActivityV2Read(BaseModel):
|
100
|
+
id: int
|
101
|
+
user_id: int
|
102
|
+
taskgroupv2_id: Optional[int] = None
|
103
|
+
timestamp_started: datetime
|
104
|
+
timestamp_ended: Optional[datetime] = None
|
105
|
+
pkg_name: str
|
106
|
+
version: str
|
107
|
+
status: TaskGroupActivityStatusV2
|
108
|
+
action: TaskGroupActivityActionV2
|
109
|
+
log: Optional[str] = None
|