fractal-server 2.7.0a2__py3-none-any.whl → 2.7.0a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +5 -13
- fractal_server/app/routes/admin/v2/task_group.py +21 -0
- fractal_server/app/routes/api/v1/task_collection.py +2 -2
- 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 +148 -187
- fractal_server/app/routes/api/v2/task_collection_custom.py +31 -58
- fractal_server/app/routes/api/v2/task_group.py +25 -1
- fractal_server/app/routes/api/v2/workflow.py +18 -34
- 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 -1
- 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 -5
- 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 -2
- 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/v1/_TaskCollectPip.py +1 -1
- fractal_server/tasks/v1/background_operations.py +2 -2
- fractal_server/tasks/v2/_venv_pip.py +62 -70
- fractal_server/tasks/v2/background_operations.py +168 -49
- 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 +0 -134
- 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.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/METADATA +1 -1
- {fractal_server-2.7.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/RECORD +45 -48
- 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.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/LICENSE +0 -0
- {fractal_server-2.7.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/WHEEL +0 -0
- {fractal_server-2.7.0a2.dist-info → fractal_server-2.7.0a4.dist-info}/entry_points.txt +0 -0
@@ -7,8 +7,6 @@ from fastapi import Response
|
|
7
7
|
from fastapi import status
|
8
8
|
from sqlmodel import select
|
9
9
|
|
10
|
-
from .....logger import reset_logger_handlers
|
11
|
-
from .....logger import set_logger
|
12
10
|
from ....db import AsyncSession
|
13
11
|
from ....db import get_async_db
|
14
12
|
from ....models.v2 import JobV2
|
@@ -27,7 +25,7 @@ from ._aux_functions import _get_project_check_owner
|
|
27
25
|
from ._aux_functions import _get_submitted_jobs_statement
|
28
26
|
from ._aux_functions import _get_workflow_check_owner
|
29
27
|
from ._aux_functions import _workflow_insert_task
|
30
|
-
from ._aux_functions_tasks import
|
28
|
+
from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
|
31
29
|
from fractal_server.app.models import UserOAuth
|
32
30
|
from fractal_server.app.routes.auth import current_active_user
|
33
31
|
|
@@ -110,31 +108,21 @@ async def read_workflow(
|
|
110
108
|
db=db,
|
111
109
|
)
|
112
110
|
|
111
|
+
wftask_list_with_warnings = await _add_warnings_to_workflow_tasks(
|
112
|
+
wftask_list=workflow.task_list, user_id=user.id, db=db
|
113
|
+
)
|
113
114
|
workflow_data = dict(
|
114
115
|
**workflow.model_dump(),
|
115
116
|
project=workflow.project,
|
117
|
+
task_list=wftask_list_with_warnings,
|
116
118
|
)
|
117
|
-
|
118
|
-
for wftask in workflow.task_list:
|
119
|
-
wftask_data = dict(wftask.model_dump(), task=wftask.task)
|
120
|
-
try:
|
121
|
-
task_group = await _get_task_group_read_access(
|
122
|
-
task_group_id=wftask.task.taskgroupv2_id,
|
123
|
-
user_id=user.id,
|
124
|
-
db=db,
|
125
|
-
)
|
126
|
-
if not task_group.active:
|
127
|
-
wftask_data["warning"] = "Task is not active."
|
128
|
-
except HTTPException:
|
129
|
-
wftask_data["warning"] = "Current user has no access to this task."
|
130
|
-
task_list_with_warnings.append(wftask_data)
|
131
|
-
workflow_data["task_list"] = task_list_with_warnings
|
119
|
+
|
132
120
|
return workflow_data
|
133
121
|
|
134
122
|
|
135
123
|
@router.patch(
|
136
124
|
"/project/{project_id}/workflow/{workflow_id}/",
|
137
|
-
response_model=
|
125
|
+
response_model=WorkflowReadV2WithWarnings,
|
138
126
|
)
|
139
127
|
async def update_workflow(
|
140
128
|
project_id: int,
|
@@ -142,7 +130,7 @@ async def update_workflow(
|
|
142
130
|
patch: WorkflowUpdateV2,
|
143
131
|
user: UserOAuth = Depends(current_active_user),
|
144
132
|
db: AsyncSession = Depends(get_async_db),
|
145
|
-
) -> Optional[
|
133
|
+
) -> Optional[WorkflowReadV2WithWarnings]:
|
146
134
|
"""
|
147
135
|
Edit a workflow
|
148
136
|
"""
|
@@ -184,7 +172,16 @@ async def update_workflow(
|
|
184
172
|
await db.refresh(workflow)
|
185
173
|
await db.close()
|
186
174
|
|
187
|
-
|
175
|
+
wftask_list_with_warnings = await _add_warnings_to_workflow_tasks(
|
176
|
+
wftask_list=workflow.task_list, user_id=user.id, db=db
|
177
|
+
)
|
178
|
+
workflow_data = dict(
|
179
|
+
**workflow.model_dump(),
|
180
|
+
project=workflow.project,
|
181
|
+
task_list=wftask_list_with_warnings,
|
182
|
+
)
|
183
|
+
|
184
|
+
return workflow_data
|
188
185
|
|
189
186
|
|
190
187
|
@router.delete(
|
@@ -259,19 +256,6 @@ async def export_worfklow(
|
|
259
256
|
user_id=user.id,
|
260
257
|
db=db,
|
261
258
|
)
|
262
|
-
# Emit a warning when exporting a workflow with custom tasks
|
263
|
-
logger = set_logger(None)
|
264
|
-
for wftask in workflow.task_list:
|
265
|
-
if wftask.task.owner is not None:
|
266
|
-
logger.warning(
|
267
|
-
f"Custom tasks (like the one with id={wftask.task_id} and "
|
268
|
-
f'source="{wftask.task.source}") are not meant to be '
|
269
|
-
"portable; re-importing this workflow may not work as "
|
270
|
-
"expected."
|
271
|
-
)
|
272
|
-
reset_logger_handlers(logger)
|
273
|
-
|
274
|
-
await db.close()
|
275
259
|
return workflow
|
276
260
|
|
277
261
|
|
@@ -66,7 +66,7 @@ async def _get_single_user_with_groups(
|
|
66
66
|
)
|
67
67
|
|
68
68
|
|
69
|
-
async def
|
69
|
+
async def _get_single_usergroup_with_user_ids(
|
70
70
|
group_id: int, db: AsyncSession
|
71
71
|
) -> UserGroupRead:
|
72
72
|
"""
|
@@ -80,15 +80,7 @@ async def _get_single_group_with_user_ids(
|
|
80
80
|
`UserGroupRead` object, with `user_ids` attribute populated
|
81
81
|
from database.
|
82
82
|
"""
|
83
|
-
|
84
|
-
stm_group = select(UserGroup).where(UserGroup.id == group_id)
|
85
|
-
res = await db.execute(stm_group)
|
86
|
-
group = res.scalars().one_or_none()
|
87
|
-
if group is None:
|
88
|
-
raise HTTPException(
|
89
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
90
|
-
detail=f"Group {group_id} not found.",
|
91
|
-
)
|
83
|
+
group = await _usergroup_or_404(group_id, db)
|
92
84
|
|
93
85
|
# Get all user/group links
|
94
86
|
stm_links = select(LinkUserGroup).where(LinkUserGroup.group_id == group_id)
|
@@ -110,12 +102,23 @@ async def _user_or_404(user_id: int, db: AsyncSession) -> UserOAuth:
|
|
110
102
|
user = await db.get(UserOAuth, user_id, populate_existing=True)
|
111
103
|
if user is None:
|
112
104
|
raise HTTPException(
|
113
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
105
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
106
|
+
detail=f"User {user_id} not found.",
|
107
|
+
)
|
108
|
+
return user
|
109
|
+
|
110
|
+
|
111
|
+
async def _usergroup_or_404(usergroup_id: int, db: AsyncSession) -> UserGroup:
|
112
|
+
user = await db.get(UserGroup, usergroup_id)
|
113
|
+
if user is None:
|
114
|
+
raise HTTPException(
|
115
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
116
|
+
detail=f"UserGroup {usergroup_id} not found.",
|
114
117
|
)
|
115
118
|
return user
|
116
119
|
|
117
120
|
|
118
|
-
async def
|
121
|
+
async def _get_default_usergroup_id(db: AsyncSession) -> int:
|
119
122
|
stm = select(UserGroup.id).where(
|
120
123
|
UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
|
121
124
|
)
|
@@ -4,6 +4,7 @@ Definition of `/auth/group/` routes
|
|
4
4
|
from fastapi import APIRouter
|
5
5
|
from fastapi import Depends
|
6
6
|
from fastapi import HTTPException
|
7
|
+
from fastapi import Response
|
7
8
|
from fastapi import status
|
8
9
|
from sqlalchemy.exc import IntegrityError
|
9
10
|
from sqlalchemy.ext.asyncio import AsyncSession
|
@@ -12,14 +13,17 @@ from sqlmodel import func
|
|
12
13
|
from sqlmodel import select
|
13
14
|
|
14
15
|
from . import current_active_superuser
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from ...schemas.user_group import UserGroupUpdate
|
19
|
-
from ._aux_auth import _get_single_group_with_user_ids
|
16
|
+
from ._aux_auth import _get_single_usergroup_with_user_ids
|
17
|
+
from ._aux_auth import _usergroup_or_404
|
18
|
+
from fractal_server.app.db import get_async_db
|
20
19
|
from fractal_server.app.models import LinkUserGroup
|
21
20
|
from fractal_server.app.models import UserGroup
|
22
21
|
from fractal_server.app.models import UserOAuth
|
22
|
+
from fractal_server.app.models.v2 import TaskGroupV2
|
23
|
+
from fractal_server.app.schemas.user_group import UserGroupCreate
|
24
|
+
from fractal_server.app.schemas.user_group import UserGroupRead
|
25
|
+
from fractal_server.app.schemas.user_group import UserGroupUpdate
|
26
|
+
from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
|
23
27
|
from fractal_server.logger import set_logger
|
24
28
|
|
25
29
|
logger = set_logger(__name__)
|
@@ -70,7 +74,7 @@ async def get_single_user_group(
|
|
70
74
|
user: UserOAuth = Depends(current_active_superuser),
|
71
75
|
db: AsyncSession = Depends(get_async_db),
|
72
76
|
) -> UserGroupRead:
|
73
|
-
group = await
|
77
|
+
group = await _get_single_usergroup_with_user_ids(group_id=group_id, db=db)
|
74
78
|
return group
|
75
79
|
|
76
80
|
|
@@ -118,12 +122,7 @@ async def update_single_group(
|
|
118
122
|
db: AsyncSession = Depends(get_async_db),
|
119
123
|
) -> UserGroupRead:
|
120
124
|
|
121
|
-
group = await
|
122
|
-
if group is None:
|
123
|
-
raise HTTPException(
|
124
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
125
|
-
detail=f"UserGroup {group_id} not found.",
|
126
|
-
)
|
125
|
+
group = await _usergroup_or_404(group_id, db)
|
127
126
|
|
128
127
|
# Check that all required users exist
|
129
128
|
# Note: The reason for introducing `col` is as in
|
@@ -167,25 +166,49 @@ async def update_single_group(
|
|
167
166
|
db.add(group)
|
168
167
|
await db.commit()
|
169
168
|
|
170
|
-
updated_group = await
|
169
|
+
updated_group = await _get_single_usergroup_with_user_ids(
|
171
170
|
group_id=group_id, db=db
|
172
171
|
)
|
173
172
|
|
174
173
|
return updated_group
|
175
174
|
|
176
175
|
|
177
|
-
@router_group.delete(
|
178
|
-
"/group/{group_id}/", status_code=status.HTTP_405_METHOD_NOT_ALLOWED
|
179
|
-
)
|
176
|
+
@router_group.delete("/group/{group_id}/", status_code=204)
|
180
177
|
async def delete_single_group(
|
181
178
|
group_id: int,
|
182
179
|
user: UserOAuth = Depends(current_active_superuser),
|
183
180
|
db: AsyncSession = Depends(get_async_db),
|
184
|
-
) ->
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
181
|
+
) -> Response:
|
182
|
+
|
183
|
+
group = await _usergroup_or_404(group_id, db)
|
184
|
+
|
185
|
+
if group.name == FRACTAL_DEFAULT_GROUP_NAME:
|
186
|
+
raise HTTPException(
|
187
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
188
|
+
detail=(
|
189
|
+
"Cannot delete default UserGroup "
|
190
|
+
f"'{FRACTAL_DEFAULT_GROUP_NAME}'."
|
191
|
+
),
|
192
|
+
)
|
193
|
+
|
194
|
+
# Cascade operations
|
195
|
+
|
196
|
+
res = await db.execute(
|
197
|
+
select(LinkUserGroup).where(LinkUserGroup.group_id == group_id)
|
198
|
+
)
|
199
|
+
for link in res.scalars().all():
|
200
|
+
await db.delete(link)
|
201
|
+
|
202
|
+
res = await db.execute(
|
203
|
+
select(TaskGroupV2).where(TaskGroupV2.user_group_id == group_id)
|
191
204
|
)
|
205
|
+
for task_group in res.scalars().all():
|
206
|
+
task_group.user_group_id = None
|
207
|
+
db.add(task_group)
|
208
|
+
|
209
|
+
# Delete
|
210
|
+
|
211
|
+
await db.delete(group)
|
212
|
+
await db.commit()
|
213
|
+
|
214
|
+
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
3
|
from pydantic import BaseModel
|
4
|
+
from pydantic import Extra
|
4
5
|
from pydantic import Field
|
5
6
|
from pydantic import validator
|
6
7
|
|
@@ -9,9 +10,7 @@ from fractal_server.images import Filters
|
|
9
10
|
from fractal_server.urls import normalize_url
|
10
11
|
|
11
12
|
|
12
|
-
class TaskOutput(BaseModel):
|
13
|
-
class Config:
|
14
|
-
extra = "forbid"
|
13
|
+
class TaskOutput(BaseModel, extra=Extra.forbid):
|
15
14
|
|
16
15
|
image_list_updates: list[SingleImageTaskOutput] = Field(
|
17
16
|
default_factory=list
|
@@ -43,9 +42,7 @@ class TaskOutput(BaseModel):
|
|
43
42
|
return [normalize_url(zarr_url) for zarr_url in v]
|
44
43
|
|
45
44
|
|
46
|
-
class InitArgsModel(BaseModel):
|
47
|
-
class Config:
|
48
|
-
extra = "forbid"
|
45
|
+
class InitArgsModel(BaseModel, extra=Extra.forbid):
|
49
46
|
|
50
47
|
zarr_url: str
|
51
48
|
init_args: dict[str, Any] = Field(default_factory=dict)
|
@@ -55,8 +52,6 @@ class InitArgsModel(BaseModel):
|
|
55
52
|
return normalize_url(v)
|
56
53
|
|
57
54
|
|
58
|
-
class InitTaskOutput(BaseModel):
|
59
|
-
class Config:
|
60
|
-
extra = "forbid"
|
55
|
+
class InitTaskOutput(BaseModel, extra=Extra.forbid):
|
61
56
|
|
62
57
|
parallelization_list: list[InitArgsModel] = Field(default_factory=list)
|
@@ -66,9 +66,7 @@ class DatasetReadV2(BaseModel):
|
|
66
66
|
)
|
67
67
|
|
68
68
|
|
69
|
-
class DatasetUpdateV2(BaseModel):
|
70
|
-
class Config:
|
71
|
-
extra = "forbid"
|
69
|
+
class DatasetUpdateV2(BaseModel, extra=Extra.forbid):
|
72
70
|
|
73
71
|
name: Optional[str]
|
74
72
|
zarr_dir: Optional[str]
|
@@ -84,7 +82,7 @@ class DatasetUpdateV2(BaseModel):
|
|
84
82
|
_name = validator("name", allow_reuse=True)(valstr("name"))
|
85
83
|
|
86
84
|
|
87
|
-
class DatasetImportV2(BaseModel):
|
85
|
+
class DatasetImportV2(BaseModel, extra=Extra.forbid):
|
88
86
|
"""
|
89
87
|
Class for `Dataset` import.
|
90
88
|
|
@@ -95,9 +93,6 @@ class DatasetImportV2(BaseModel):
|
|
95
93
|
filters:
|
96
94
|
"""
|
97
95
|
|
98
|
-
class Config:
|
99
|
-
extra = "forbid"
|
100
|
-
|
101
96
|
name: str
|
102
97
|
zarr_dir: str
|
103
98
|
images: list[SingleImage] = Field(default_factory=[])
|
@@ -21,7 +21,7 @@ class TaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
21
21
|
|
22
22
|
command_non_parallel: Optional[str] = None
|
23
23
|
command_parallel: Optional[str] = None
|
24
|
-
source: str
|
24
|
+
source: Optional[str] = None
|
25
25
|
|
26
26
|
meta_non_parallel: Optional[dict[str, Any]] = None
|
27
27
|
meta_parallel: Optional[dict[str, Any]] = None
|
@@ -111,7 +111,7 @@ class TaskReadV2(BaseModel):
|
|
111
111
|
id: int
|
112
112
|
name: str
|
113
113
|
type: Literal["parallel", "non_parallel", "compound"]
|
114
|
-
source: str
|
114
|
+
source: Optional[str] = None
|
115
115
|
version: Optional[str] = None
|
116
116
|
|
117
117
|
command_non_parallel: Optional[str] = None
|
@@ -134,7 +134,7 @@ class TaskReadV2(BaseModel):
|
|
134
134
|
tags: list[str]
|
135
135
|
|
136
136
|
|
137
|
-
class TaskUpdateV2(BaseModel):
|
137
|
+
class TaskUpdateV2(BaseModel, extra=Extra.forbid):
|
138
138
|
|
139
139
|
name: Optional[str] = None
|
140
140
|
version: Optional[str] = None
|
@@ -189,7 +189,7 @@ class TaskUpdateV2(BaseModel):
|
|
189
189
|
return val_unique_list("tags")(value)
|
190
190
|
|
191
191
|
|
192
|
-
class TaskImportV2(BaseModel):
|
192
|
+
class TaskImportV2(BaseModel, extra=Extra.forbid):
|
193
193
|
|
194
194
|
source: str
|
195
195
|
_source = validator("source", allow_reuse=True)(valstr("source"))
|
@@ -197,5 +197,5 @@ class TaskImportV2(BaseModel):
|
|
197
197
|
|
198
198
|
class TaskExportV2(BaseModel):
|
199
199
|
|
200
|
-
source: str
|
200
|
+
source: Optional[str] = None
|
201
201
|
_source = validator("source", allow_reuse=True)(valstr("source"))
|
@@ -6,6 +6,7 @@ from typing import Literal
|
|
6
6
|
from typing import Optional
|
7
7
|
|
8
8
|
from pydantic import BaseModel
|
9
|
+
from pydantic import Extra
|
9
10
|
from pydantic import root_validator
|
10
11
|
from pydantic import validator
|
11
12
|
|
@@ -24,7 +25,7 @@ class CollectionStatusV2(str, Enum):
|
|
24
25
|
OK = "OK"
|
25
26
|
|
26
27
|
|
27
|
-
class TaskCollectPipV2(BaseModel):
|
28
|
+
class TaskCollectPipV2(BaseModel, extra=Extra.forbid):
|
28
29
|
"""
|
29
30
|
TaskCollectPipV2 class
|
30
31
|
|
@@ -70,7 +71,7 @@ class TaskCollectPipV2(BaseModel):
|
|
70
71
|
|
71
72
|
@validator("package")
|
72
73
|
def package_validator(cls, value):
|
73
|
-
if "/" in value:
|
74
|
+
if "/" in value or value.endswith(".whl"):
|
74
75
|
if not value.endswith(".whl"):
|
75
76
|
raise ValueError(
|
76
77
|
"Local-package path must be a wheel file "
|
@@ -92,14 +93,15 @@ class TaskCollectPipV2(BaseModel):
|
|
92
93
|
return v
|
93
94
|
|
94
95
|
|
95
|
-
class TaskCollectCustomV2(BaseModel):
|
96
|
+
class TaskCollectCustomV2(BaseModel, extra=Extra.forbid):
|
96
97
|
"""
|
97
98
|
Attributes:
|
98
99
|
manifest: Manifest of a Fractal task package (this is typically the
|
99
100
|
content of `__FRACTAL_MANIFEST__.json`).
|
100
101
|
python_interpreter: Absolute path to the Python interpreter to be used
|
101
102
|
for running tasks.
|
102
|
-
|
103
|
+
name: A name identifying this package, that will fill the
|
104
|
+
`TaskGroupV2.pkg_name` column.
|
103
105
|
package_root: The folder where the package is installed.
|
104
106
|
If not provided, it will be extracted via `pip show`
|
105
107
|
(requires `package_name` to be set).
|
@@ -111,7 +113,7 @@ class TaskCollectCustomV2(BaseModel):
|
|
111
113
|
|
112
114
|
manifest: ManifestV2
|
113
115
|
python_interpreter: str
|
114
|
-
|
116
|
+
label: str
|
115
117
|
package_root: Optional[str]
|
116
118
|
package_name: Optional[str]
|
117
119
|
version: Optional[str]
|
@@ -120,7 +122,7 @@ class TaskCollectCustomV2(BaseModel):
|
|
120
122
|
_python_interpreter = validator("python_interpreter", allow_reuse=True)(
|
121
123
|
valstr("python_interpreter")
|
122
124
|
)
|
123
|
-
|
125
|
+
_label = validator("label", allow_reuse=True)(valstr("label"))
|
124
126
|
_package_root = validator("package_root", allow_reuse=True)(
|
125
127
|
valstr("package_root", accept_none=True)
|
126
128
|
)
|
@@ -3,12 +3,19 @@ from typing import Literal
|
|
3
3
|
from typing import Optional
|
4
4
|
|
5
5
|
from pydantic import BaseModel
|
6
|
+
from pydantic import Extra
|
7
|
+
from pydantic import Field
|
6
8
|
from pydantic import validator
|
7
9
|
|
10
|
+
from .._validators import val_absolute_path
|
11
|
+
from .._validators import valdictkeys
|
12
|
+
from .._validators import valstr
|
8
13
|
from .task import TaskReadV2
|
9
14
|
|
10
15
|
|
11
|
-
class TaskGroupCreateV2(BaseModel):
|
16
|
+
class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
|
17
|
+
user_id: int
|
18
|
+
user_group_id: Optional[int] = None
|
12
19
|
active: bool = True
|
13
20
|
origin: Literal["pypi", "wheel-file", "other"]
|
14
21
|
pkg_name: str
|
@@ -16,11 +23,30 @@ class TaskGroupCreateV2(BaseModel):
|
|
16
23
|
python_version: Optional[str] = None
|
17
24
|
path: Optional[str] = None
|
18
25
|
venv_path: Optional[str] = None
|
26
|
+
wheel_path: Optional[str] = None
|
19
27
|
pip_extras: Optional[str] = None
|
28
|
+
pinned_package_versions: dict[str, str] = Field(default_factory=dict)
|
20
29
|
|
30
|
+
# Validators
|
31
|
+
_path = validator("path", allow_reuse=True)(val_absolute_path("path"))
|
32
|
+
_venv_path = validator("venv_path", allow_reuse=True)(
|
33
|
+
val_absolute_path("venv_path")
|
34
|
+
)
|
35
|
+
_wheel_path = validator("wheel_path", allow_reuse=True)(
|
36
|
+
val_absolute_path("wheel_path")
|
37
|
+
)
|
38
|
+
_pinned_package_versions = validator(
|
39
|
+
"pinned_package_versions", allow_reuse=True
|
40
|
+
)(valdictkeys("pinned_package_versions"))
|
41
|
+
_pip_extras = validator("pip_extras", allow_reuse=True)(
|
42
|
+
valstr("pip_extras")
|
43
|
+
)
|
44
|
+
_python_version = validator("python_version", allow_reuse=True)(
|
45
|
+
valstr("python_version")
|
46
|
+
)
|
21
47
|
|
22
|
-
class TaskGroupReadV2(BaseModel):
|
23
48
|
|
49
|
+
class TaskGroupReadV2(BaseModel):
|
24
50
|
id: int
|
25
51
|
task_list: list[TaskReadV2]
|
26
52
|
|
@@ -33,13 +59,15 @@ class TaskGroupReadV2(BaseModel):
|
|
33
59
|
python_version: Optional[str] = None
|
34
60
|
path: Optional[str] = None
|
35
61
|
venv_path: Optional[str] = None
|
62
|
+
wheel_path: Optional[str] = None
|
36
63
|
pip_extras: Optional[str] = None
|
64
|
+
pinned_package_versions: dict[str, str] = Field(default_factory=dict)
|
37
65
|
|
38
66
|
active: bool
|
39
67
|
timestamp_created: datetime
|
40
68
|
|
41
69
|
|
42
|
-
class TaskGroupUpdateV2(BaseModel):
|
70
|
+
class TaskGroupUpdateV2(BaseModel, extra=Extra.forbid):
|
43
71
|
user_group_id: Optional[int] = None
|
44
72
|
active: Optional[bool] = None
|
45
73
|
|
@@ -40,7 +40,7 @@ class WorkflowReadV2WithWarnings(WorkflowReadV2):
|
|
40
40
|
task_list: list[WorkflowTaskReadV2WithWarning]
|
41
41
|
|
42
42
|
|
43
|
-
class WorkflowUpdateV2(BaseModel):
|
43
|
+
class WorkflowUpdateV2(BaseModel, extra=Extra.forbid):
|
44
44
|
|
45
45
|
name: Optional[str]
|
46
46
|
reordered_workflowtask_ids: Optional[list[int]]
|
@@ -57,7 +57,7 @@ class WorkflowUpdateV2(BaseModel):
|
|
57
57
|
return value
|
58
58
|
|
59
59
|
|
60
|
-
class WorkflowImportV2(BaseModel):
|
60
|
+
class WorkflowImportV2(BaseModel, extra=Extra.forbid):
|
61
61
|
"""
|
62
62
|
Class for `Workflow` import.
|
63
63
|
|
@@ -106,7 +106,7 @@ class WorkflowTaskReadV2WithWarning(WorkflowTaskReadV2):
|
|
106
106
|
warning: Optional[str] = None
|
107
107
|
|
108
108
|
|
109
|
-
class WorkflowTaskUpdateV2(BaseModel):
|
109
|
+
class WorkflowTaskUpdateV2(BaseModel, extra=Extra.forbid):
|
110
110
|
|
111
111
|
meta_non_parallel: Optional[dict[str, Any]]
|
112
112
|
meta_parallel: Optional[dict[str, Any]]
|
@@ -151,7 +151,7 @@ class WorkflowTaskUpdateV2(BaseModel):
|
|
151
151
|
return value
|
152
152
|
|
153
153
|
|
154
|
-
class WorkflowTaskImportV2(BaseModel):
|
154
|
+
class WorkflowTaskImportV2(BaseModel, extra=Extra.forbid):
|
155
155
|
|
156
156
|
meta_non_parallel: Optional[dict[str, Any]] = None
|
157
157
|
meta_parallel: Optional[dict[str, Any]] = None
|
@@ -110,7 +110,6 @@ def prepare_task_groups(
|
|
110
110
|
user_mapping: dict[str, int],
|
111
111
|
default_user_group_id: int,
|
112
112
|
default_user_id: int,
|
113
|
-
dry_run: bool,
|
114
113
|
db: Session,
|
115
114
|
):
|
116
115
|
stm_tasks = select(TaskV2).order_by(TaskV2.id)
|
@@ -236,14 +235,6 @@ def prepare_task_groups(
|
|
236
235
|
|
237
236
|
print()
|
238
237
|
|
239
|
-
if dry_run:
|
240
|
-
print(
|
241
|
-
"End dry-run of handling task group with key "
|
242
|
-
f"'{task_group_key}"
|
243
|
-
)
|
244
|
-
print("-" * 80)
|
245
|
-
continue
|
246
|
-
|
247
238
|
task_group = TaskGroupV2(**task_group_attributes)
|
248
239
|
db.add(task_group)
|
249
240
|
db.commit()
|
@@ -254,7 +245,7 @@ def prepare_task_groups(
|
|
254
245
|
return
|
255
246
|
|
256
247
|
|
257
|
-
def fix_db(
|
248
|
+
def fix_db():
|
258
249
|
logger.warning("START execution of fix_db function")
|
259
250
|
_check_current_version("2.7.0")
|
260
251
|
|
@@ -268,7 +259,6 @@ def fix_db(dry_run: bool = False):
|
|
268
259
|
default_user_id=default_user_id,
|
269
260
|
default_user_group_id=default_user_group_id,
|
270
261
|
db=db,
|
271
|
-
dry_run=dry_run,
|
272
262
|
)
|
273
263
|
|
274
264
|
logger.warning("END of execution of fix_db function")
|
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
|