fractal-server 2.7.0a3__py3-none-any.whl → 2.7.0a5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fractal_server/__init__.py +1 -1
- fractal_server/__main__.py +3 -9
- fractal_server/app/models/v2/collection_state.py +1 -0
- fractal_server/app/models/v2/task.py +27 -3
- fractal_server/app/routes/admin/v2/task.py +4 -17
- fractal_server/app/routes/admin/v2/task_group.py +21 -0
- fractal_server/app/routes/api/v1/task_collection.py +4 -4
- fractal_server/app/routes/api/v2/_aux_functions.py +1 -7
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +75 -2
- fractal_server/app/routes/api/v2/task.py +16 -42
- fractal_server/app/routes/api/v2/task_collection.py +175 -204
- fractal_server/app/routes/api/v2/task_collection_custom.py +31 -58
- fractal_server/app/routes/api/v2/task_group.py +29 -1
- fractal_server/app/routes/api/v2/workflow.py +11 -46
- fractal_server/app/routes/api/v2/workflowtask.py +0 -1
- fractal_server/app/routes/auth/_aux_auth.py +15 -12
- fractal_server/app/routes/auth/group.py +46 -23
- fractal_server/app/runner/v2/task_interface.py +4 -9
- fractal_server/app/schemas/v2/dataset.py +2 -7
- fractal_server/app/schemas/v2/dumps.py +1 -2
- fractal_server/app/schemas/v2/job.py +1 -1
- fractal_server/app/schemas/v2/project.py +1 -1
- fractal_server/app/schemas/v2/task.py +5 -10
- fractal_server/app/schemas/v2/task_collection.py +8 -6
- fractal_server/app/schemas/v2/task_group.py +31 -3
- fractal_server/app/schemas/v2/workflow.py +2 -2
- fractal_server/app/schemas/v2/workflowtask.py +2 -5
- fractal_server/data_migrations/2_7_0.py +1 -11
- fractal_server/images/models.py +2 -4
- fractal_server/main.py +1 -1
- fractal_server/migrations/versions/034a469ec2eb_task_groups.py +184 -0
- fractal_server/string_tools.py +6 -2
- fractal_server/tasks/utils.py +19 -5
- fractal_server/tasks/v1/_TaskCollectPip.py +1 -1
- fractal_server/tasks/v1/background_operations.py +5 -5
- fractal_server/tasks/v1/get_collection_data.py +2 -2
- fractal_server/tasks/v2/_venv_pip.py +62 -70
- fractal_server/tasks/v2/background_operations.py +170 -51
- fractal_server/tasks/v2/background_operations_ssh.py +35 -77
- fractal_server/tasks/v2/database_operations.py +7 -17
- fractal_server/tasks/v2/endpoint_operations.py +91 -145
- fractal_server/tasks/v2/templates/_1_create_venv.sh +9 -5
- fractal_server/tasks/v2/utils.py +5 -0
- fractal_server/utils.py +3 -2
- {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/METADATA +1 -1
- {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/RECORD +49 -52
- fractal_server/migrations/versions/742b74e1cc6e_revamp_taskv2_and_taskgroupv2.py +0 -101
- fractal_server/migrations/versions/7cf1baae8fb4_task_group_v2.py +0 -66
- fractal_server/migrations/versions/df7cc3501bf7_linkusergroup_timestamp_created.py +0 -42
- fractal_server/tasks/v2/_TaskCollectPip.py +0 -132
- {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/LICENSE +0 -0
- {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/WHEEL +0 -0
- {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a5.dist-info}/entry_points.txt +0 -0
@@ -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,25 +108,15 @@ 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
|
|
@@ -184,25 +172,15 @@ async def update_workflow(
|
|
184
172
|
await db.refresh(workflow)
|
185
173
|
await db.close()
|
186
174
|
|
175
|
+
wftask_list_with_warnings = await _add_warnings_to_workflow_tasks(
|
176
|
+
wftask_list=workflow.task_list, user_id=user.id, db=db
|
177
|
+
)
|
187
178
|
workflow_data = dict(
|
188
179
|
**workflow.model_dump(),
|
189
180
|
project=workflow.project,
|
181
|
+
task_list=wftask_list_with_warnings,
|
190
182
|
)
|
191
|
-
|
192
|
-
for wftask in workflow.task_list:
|
193
|
-
wftask_data = dict(wftask.model_dump(), task=wftask.task)
|
194
|
-
try:
|
195
|
-
task_group = await _get_task_group_read_access(
|
196
|
-
task_group_id=wftask.task.taskgroupv2_id,
|
197
|
-
user_id=user.id,
|
198
|
-
db=db,
|
199
|
-
)
|
200
|
-
if not task_group.active:
|
201
|
-
wftask_data["warning"] = "Task is not active."
|
202
|
-
except HTTPException:
|
203
|
-
wftask_data["warning"] = "Current user has no access to this task."
|
204
|
-
task_list_with_warnings.append(wftask_data)
|
205
|
-
workflow_data["task_list"] = task_list_with_warnings
|
183
|
+
|
206
184
|
return workflow_data
|
207
185
|
|
208
186
|
|
@@ -278,19 +256,6 @@ async def export_worfklow(
|
|
278
256
|
user_id=user.id,
|
279
257
|
db=db,
|
280
258
|
)
|
281
|
-
# Emit a warning when exporting a workflow with custom tasks
|
282
|
-
logger = set_logger(None)
|
283
|
-
for wftask in workflow.task_list:
|
284
|
-
if wftask.task.owner is not None:
|
285
|
-
logger.warning(
|
286
|
-
f"Custom tasks (like the one with id={wftask.task_id} and "
|
287
|
-
f'source="{wftask.task.source}") are not meant to be '
|
288
|
-
"portable; re-importing this workflow may not work as "
|
289
|
-
"expected."
|
290
|
-
)
|
291
|
-
reset_logger_handlers(logger)
|
292
|
-
|
293
|
-
await db.close()
|
294
259
|
return workflow
|
295
260
|
|
296
261
|
|
@@ -77,7 +77,6 @@ async def create_workflowtask(
|
|
77
77
|
workflow_task = await _workflow_insert_task(
|
78
78
|
workflow_id=workflow.id,
|
79
79
|
task_id=task_id,
|
80
|
-
order=new_task.order,
|
81
80
|
meta_non_parallel=new_task.meta_non_parallel,
|
82
81
|
meta_parallel=new_task.meta_parallel,
|
83
82
|
args_non_parallel=new_task.args_non_parallel,
|
@@ -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,6 @@ 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
|
25
24
|
|
26
25
|
meta_non_parallel: Optional[dict[str, Any]] = None
|
27
26
|
meta_parallel: Optional[dict[str, Any]] = None
|
@@ -64,7 +63,6 @@ class TaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
64
63
|
_command_parallel = validator("command_parallel", allow_reuse=True)(
|
65
64
|
valstr("command_parallel")
|
66
65
|
)
|
67
|
-
_source = validator("source", allow_reuse=True)(valstr("source"))
|
68
66
|
_version = validator("version", allow_reuse=True)(valstr("version"))
|
69
67
|
|
70
68
|
_meta_non_parallel = validator("meta_non_parallel", allow_reuse=True)(
|
@@ -111,7 +109,7 @@ class TaskReadV2(BaseModel):
|
|
111
109
|
id: int
|
112
110
|
name: str
|
113
111
|
type: Literal["parallel", "non_parallel", "compound"]
|
114
|
-
source: str
|
112
|
+
source: Optional[str] = None
|
115
113
|
version: Optional[str] = None
|
116
114
|
|
117
115
|
command_non_parallel: Optional[str] = None
|
@@ -134,10 +132,9 @@ class TaskReadV2(BaseModel):
|
|
134
132
|
tags: list[str]
|
135
133
|
|
136
134
|
|
137
|
-
class TaskUpdateV2(BaseModel):
|
135
|
+
class TaskUpdateV2(BaseModel, extra=Extra.forbid):
|
138
136
|
|
139
137
|
name: Optional[str] = None
|
140
|
-
version: Optional[str] = None
|
141
138
|
command_parallel: Optional[str] = None
|
142
139
|
command_non_parallel: Optional[str] = None
|
143
140
|
input_types: Optional[dict[str, bool]] = None
|
@@ -156,9 +153,7 @@ class TaskUpdateV2(BaseModel):
|
|
156
153
|
return v
|
157
154
|
|
158
155
|
_name = validator("name", allow_reuse=True)(valstr("name"))
|
159
|
-
|
160
|
-
valstr("version", accept_none=True)
|
161
|
-
)
|
156
|
+
|
162
157
|
_command_parallel = validator("command_parallel", allow_reuse=True)(
|
163
158
|
valstr("command_parallel")
|
164
159
|
)
|
@@ -189,7 +184,7 @@ class TaskUpdateV2(BaseModel):
|
|
189
184
|
return val_unique_list("tags")(value)
|
190
185
|
|
191
186
|
|
192
|
-
class TaskImportV2(BaseModel):
|
187
|
+
class TaskImportV2(BaseModel, extra=Extra.forbid):
|
193
188
|
|
194
189
|
source: str
|
195
190
|
_source = validator("source", allow_reuse=True)(valstr("source"))
|
@@ -197,5 +192,5 @@ class TaskImportV2(BaseModel):
|
|
197
192
|
|
198
193
|
class TaskExportV2(BaseModel):
|
199
194
|
|
200
|
-
source: str
|
195
|
+
source: Optional[str] = None
|
201
196
|
_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
|
|
@@ -8,7 +8,6 @@ from pydantic import Field
|
|
8
8
|
from pydantic import validator
|
9
9
|
|
10
10
|
from .._validators import valdictkeys
|
11
|
-
from .._validators import valint
|
12
11
|
from .task import TaskExportV2
|
13
12
|
from .task import TaskImportV2
|
14
13
|
from .task import TaskReadV2
|
@@ -42,7 +41,6 @@ class WorkflowTaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
42
41
|
meta_parallel: Optional[dict[str, Any]]
|
43
42
|
args_non_parallel: Optional[dict[str, Any]]
|
44
43
|
args_parallel: Optional[dict[str, Any]]
|
45
|
-
order: Optional[int]
|
46
44
|
input_filters: Filters = Field(default_factory=Filters)
|
47
45
|
|
48
46
|
# Validators
|
@@ -52,7 +50,6 @@ class WorkflowTaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
52
50
|
_meta_parallel = validator("meta_parallel", allow_reuse=True)(
|
53
51
|
valdictkeys("meta_parallel")
|
54
52
|
)
|
55
|
-
_order = validator("order", allow_reuse=True)(valint("order", min_val=0))
|
56
53
|
|
57
54
|
@validator("args_non_parallel")
|
58
55
|
def validate_args_non_parallel(cls, value):
|
@@ -106,7 +103,7 @@ class WorkflowTaskReadV2WithWarning(WorkflowTaskReadV2):
|
|
106
103
|
warning: Optional[str] = None
|
107
104
|
|
108
105
|
|
109
|
-
class WorkflowTaskUpdateV2(BaseModel):
|
106
|
+
class WorkflowTaskUpdateV2(BaseModel, extra=Extra.forbid):
|
110
107
|
|
111
108
|
meta_non_parallel: Optional[dict[str, Any]]
|
112
109
|
meta_parallel: Optional[dict[str, Any]]
|
@@ -151,7 +148,7 @@ class WorkflowTaskUpdateV2(BaseModel):
|
|
151
148
|
return value
|
152
149
|
|
153
150
|
|
154
|
-
class WorkflowTaskImportV2(BaseModel):
|
151
|
+
class WorkflowTaskImportV2(BaseModel, extra=Extra.forbid):
|
155
152
|
|
156
153
|
meta_non_parallel: Optional[dict[str, Any]] = None
|
157
154
|
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")
|