fractal-server 2.7.0a3__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.
Files changed (49) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +3 -9
  3. fractal_server/app/models/v2/collection_state.py +1 -0
  4. fractal_server/app/models/v2/task.py +27 -3
  5. fractal_server/app/routes/admin/v2/task.py +5 -13
  6. fractal_server/app/routes/admin/v2/task_group.py +21 -0
  7. fractal_server/app/routes/api/v1/task_collection.py +2 -2
  8. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +75 -2
  9. fractal_server/app/routes/api/v2/task.py +16 -42
  10. fractal_server/app/routes/api/v2/task_collection.py +148 -187
  11. fractal_server/app/routes/api/v2/task_collection_custom.py +31 -58
  12. fractal_server/app/routes/api/v2/task_group.py +25 -1
  13. fractal_server/app/routes/api/v2/workflow.py +11 -46
  14. fractal_server/app/routes/auth/_aux_auth.py +15 -12
  15. fractal_server/app/routes/auth/group.py +46 -23
  16. fractal_server/app/runner/v2/task_interface.py +4 -9
  17. fractal_server/app/schemas/v2/dataset.py +2 -7
  18. fractal_server/app/schemas/v2/dumps.py +1 -1
  19. fractal_server/app/schemas/v2/job.py +1 -1
  20. fractal_server/app/schemas/v2/project.py +1 -1
  21. fractal_server/app/schemas/v2/task.py +5 -5
  22. fractal_server/app/schemas/v2/task_collection.py +8 -6
  23. fractal_server/app/schemas/v2/task_group.py +31 -3
  24. fractal_server/app/schemas/v2/workflow.py +2 -2
  25. fractal_server/app/schemas/v2/workflowtask.py +2 -2
  26. fractal_server/data_migrations/2_7_0.py +1 -11
  27. fractal_server/images/models.py +2 -4
  28. fractal_server/main.py +1 -1
  29. fractal_server/migrations/versions/034a469ec2eb_task_groups.py +184 -0
  30. fractal_server/string_tools.py +6 -2
  31. fractal_server/tasks/v1/_TaskCollectPip.py +1 -1
  32. fractal_server/tasks/v1/background_operations.py +2 -2
  33. fractal_server/tasks/v2/_venv_pip.py +62 -70
  34. fractal_server/tasks/v2/background_operations.py +168 -49
  35. fractal_server/tasks/v2/background_operations_ssh.py +35 -77
  36. fractal_server/tasks/v2/database_operations.py +7 -17
  37. fractal_server/tasks/v2/endpoint_operations.py +0 -134
  38. fractal_server/tasks/v2/templates/_1_create_venv.sh +9 -5
  39. fractal_server/tasks/v2/utils.py +5 -0
  40. fractal_server/utils.py +3 -2
  41. {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a4.dist-info}/METADATA +1 -1
  42. {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a4.dist-info}/RECORD +45 -48
  43. fractal_server/migrations/versions/742b74e1cc6e_revamp_taskv2_and_taskgroupv2.py +0 -101
  44. fractal_server/migrations/versions/7cf1baae8fb4_task_group_v2.py +0 -66
  45. fractal_server/migrations/versions/df7cc3501bf7_linkusergroup_timestamp_created.py +0 -42
  46. fractal_server/tasks/v2/_TaskCollectPip.py +0 -132
  47. {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a4.dist-info}/LICENSE +0 -0
  48. {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a4.dist-info}/WHEEL +0 -0
  49. {fractal_server-2.7.0a3.dist-info → fractal_server-2.7.0a4.dist-info}/entry_points.txt +0 -0
@@ -66,7 +66,7 @@ async def _get_single_user_with_groups(
66
66
  )
67
67
 
68
68
 
69
- async def _get_single_group_with_user_ids(
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
- # Get the UserGroup object from the database
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, detail="User 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 _get_default_user_group_id(db: AsyncSession) -> int:
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 ...db import get_async_db
16
- from ...schemas.user_group import UserGroupCreate
17
- from ...schemas.user_group import UserGroupRead
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 _get_single_group_with_user_ids(group_id=group_id, db=db)
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 db.get(UserGroup, group_id)
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 _get_single_group_with_user_ids(
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
- ) -> UserGroupRead:
185
- raise HTTPException(
186
- status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
187
- detail=(
188
- "Deleting a user group is not allowed, as it may restrict "
189
- "previously-granted access.",
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=[])
@@ -30,7 +30,7 @@ class TaskDumpV2(BaseModel):
30
30
 
31
31
  command_non_parallel: Optional[str]
32
32
  command_parallel: Optional[str]
33
- source: str
33
+ source: Optional[str] = None
34
34
  owner: Optional[str]
35
35
  version: Optional[str]
36
36
 
@@ -109,6 +109,6 @@ class JobReadV2(BaseModel):
109
109
  )
110
110
 
111
111
 
112
- class JobUpdateV2(BaseModel):
112
+ class JobUpdateV2(BaseModel, extra=Extra.forbid):
113
113
 
114
114
  status: JobStatusTypeV2
@@ -27,7 +27,7 @@ class ProjectReadV2(BaseModel):
27
27
  )
28
28
 
29
29
 
30
- class ProjectUpdateV2(BaseModel):
30
+ class ProjectUpdateV2(BaseModel, extra=Extra.forbid):
31
31
 
32
32
  name: Optional[str]
33
33
  # Validators
@@ -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
- source: A common label identifying this package.
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
- source: str
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
- _source = validator("source", allow_reuse=True)(valstr("source"))
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(dry_run: bool = False):
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")
@@ -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 # FIXME: change
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