fractal-server 2.11.0a0__py3-none-any.whl → 2.11.0a2__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/dataset.py +9 -6
- fractal_server/app/models/v2/job.py +5 -0
- fractal_server/app/models/v2/workflowtask.py +5 -8
- fractal_server/app/routes/api/v2/_aux_functions.py +3 -10
- fractal_server/app/routes/api/v2/images.py +29 -6
- fractal_server/app/routes/api/v2/submit.py +5 -1
- fractal_server/app/routes/api/v2/workflowtask.py +3 -3
- fractal_server/app/runner/v2/__init__.py +1 -0
- fractal_server/app/runner/v2/_local/__init__.py +5 -0
- fractal_server/app/runner/v2/_local_experimental/__init__.py +5 -0
- fractal_server/app/runner/v2/_slurm_ssh/__init__.py +7 -3
- fractal_server/app/runner/v2/_slurm_sudo/__init__.py +5 -0
- fractal_server/app/runner/v2/merge_outputs.py +13 -16
- fractal_server/app/runner/v2/runner.py +33 -34
- fractal_server/app/runner/v2/task_interface.py +41 -2
- fractal_server/app/schemas/_filter_validators.py +47 -0
- fractal_server/app/schemas/_validators.py +13 -2
- fractal_server/app/schemas/v2/dataset.py +58 -12
- fractal_server/app/schemas/v2/dumps.py +6 -8
- fractal_server/app/schemas/v2/job.py +14 -0
- fractal_server/app/schemas/v2/task.py +9 -9
- fractal_server/app/schemas/v2/task_group.py +2 -2
- fractal_server/app/schemas/v2/workflowtask.py +42 -19
- fractal_server/data_migrations/2_11_0.py +67 -0
- fractal_server/images/__init__.py +0 -1
- fractal_server/images/models.py +12 -35
- fractal_server/images/tools.py +29 -13
- fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +96 -0
- {fractal_server-2.11.0a0.dist-info → fractal_server-2.11.0a2.dist-info}/METADATA +1 -1
- {fractal_server-2.11.0a0.dist-info → fractal_server-2.11.0a2.dist-info}/RECORD +34 -31
- {fractal_server-2.11.0a0.dist-info → fractal_server-2.11.0a2.dist-info}/LICENSE +0 -0
- {fractal_server-2.11.0a0.dist-info → fractal_server-2.11.0a2.dist-info}/WHEEL +0 -0
- {fractal_server-2.11.0a0.dist-info → fractal_server-2.11.0a2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from ._validators import valdict_keys
|
4
|
+
from fractal_server.images.models import AttributeFiltersType
|
5
|
+
|
6
|
+
|
7
|
+
def validate_type_filters(
|
8
|
+
type_filters: Optional[dict[str, bool]]
|
9
|
+
) -> dict[str, bool]:
|
10
|
+
if type_filters is None:
|
11
|
+
raise ValueError("'type_filters' cannot be 'None'.")
|
12
|
+
|
13
|
+
type_filters = valdict_keys("type_filters")(type_filters)
|
14
|
+
return type_filters
|
15
|
+
|
16
|
+
|
17
|
+
def validate_attribute_filters(
|
18
|
+
attribute_filters: Optional[AttributeFiltersType],
|
19
|
+
) -> AttributeFiltersType:
|
20
|
+
if attribute_filters is None:
|
21
|
+
raise ValueError("'attribute_filters' cannot be 'None'.")
|
22
|
+
|
23
|
+
attribute_filters = valdict_keys("attribute_filters")(attribute_filters)
|
24
|
+
for key, values in attribute_filters.items():
|
25
|
+
if values is None:
|
26
|
+
# values=None corresponds to not applying any filter for
|
27
|
+
# attribute `key`
|
28
|
+
pass
|
29
|
+
elif values == []:
|
30
|
+
# WARNING: in this case, no image can match with the current
|
31
|
+
# filter. In the future we may deprecate this possibility.
|
32
|
+
pass
|
33
|
+
else:
|
34
|
+
# values is a non-empty list, and its items must all be of the
|
35
|
+
# same scalar non-None type
|
36
|
+
_type = type(values[0])
|
37
|
+
if not all(isinstance(value, _type) for value in values):
|
38
|
+
raise ValueError(
|
39
|
+
f"attribute_filters[{key}] has values with "
|
40
|
+
f"non-homogeneous types: {values}."
|
41
|
+
)
|
42
|
+
if _type not in (int, float, str, bool):
|
43
|
+
raise ValueError(
|
44
|
+
f"attribute_filters[{key}] has values with "
|
45
|
+
f"invalid types: {values}."
|
46
|
+
)
|
47
|
+
return attribute_filters
|
@@ -27,7 +27,7 @@ def valstr(attribute: str, accept_none: bool = False):
|
|
27
27
|
return val
|
28
28
|
|
29
29
|
|
30
|
-
def
|
30
|
+
def valdict_keys(attribute: str):
|
31
31
|
def val(d: Optional[dict[str, Any]]) -> Optional[dict[str, Any]]:
|
32
32
|
"""
|
33
33
|
Apply valstr to every key of the dictionary, and fail if there are
|
@@ -38,7 +38,7 @@ def valdictkeys(attribute: str):
|
|
38
38
|
new_keys = [valstr(f"{attribute}[{key}]")(key) for key in old_keys]
|
39
39
|
if len(new_keys) != len(set(new_keys)):
|
40
40
|
raise ValueError(
|
41
|
-
f"Dictionary contains multiple identical keys: {d}."
|
41
|
+
f"Dictionary contains multiple identical keys: '{d}'."
|
42
42
|
)
|
43
43
|
for old_key, new_key in zip(old_keys, new_keys):
|
44
44
|
if new_key != old_key:
|
@@ -101,3 +101,14 @@ def val_unique_list(attribute: str):
|
|
101
101
|
return must_be_unique
|
102
102
|
|
103
103
|
return val
|
104
|
+
|
105
|
+
|
106
|
+
def root_validate_dict_keys(cls, object: dict) -> dict:
|
107
|
+
"""
|
108
|
+
For each dictionary in `object.values()`,
|
109
|
+
checks that that dictionary has only keys of type str.
|
110
|
+
"""
|
111
|
+
for dictionary in (v for v in object.values() if isinstance(v, dict)):
|
112
|
+
if not all(isinstance(key, str) for key in dictionary.keys()):
|
113
|
+
raise ValueError("Dictionary keys must be strings.")
|
114
|
+
return object
|
@@ -1,17 +1,22 @@
|
|
1
1
|
from datetime import datetime
|
2
|
+
from typing import Any
|
2
3
|
from typing import Optional
|
3
4
|
|
4
5
|
from pydantic import BaseModel
|
5
6
|
from pydantic import Extra
|
6
7
|
from pydantic import Field
|
8
|
+
from pydantic import root_validator
|
7
9
|
from pydantic import validator
|
8
10
|
|
11
|
+
from .._filter_validators import validate_attribute_filters
|
12
|
+
from .._filter_validators import validate_type_filters
|
13
|
+
from .._validators import root_validate_dict_keys
|
9
14
|
from .._validators import valstr
|
10
15
|
from .dumps import WorkflowTaskDumpV2
|
11
16
|
from .project import ProjectReadV2
|
12
17
|
from .workflowtask import WorkflowTaskStatusTypeV2
|
13
|
-
from fractal_server.images import Filters
|
14
18
|
from fractal_server.images import SingleImage
|
19
|
+
from fractal_server.images.models import AttributeFiltersType
|
15
20
|
from fractal_server.urls import normalize_url
|
16
21
|
|
17
22
|
|
@@ -34,17 +39,29 @@ class DatasetCreateV2(BaseModel, extra=Extra.forbid):
|
|
34
39
|
|
35
40
|
zarr_dir: Optional[str] = None
|
36
41
|
|
37
|
-
|
42
|
+
type_filters: dict[str, bool] = Field(default_factory=dict)
|
43
|
+
attribute_filters: AttributeFiltersType = Field(default_factory=dict)
|
38
44
|
|
39
45
|
# Validators
|
46
|
+
|
47
|
+
_dict_keys = root_validator(pre=True, allow_reuse=True)(
|
48
|
+
root_validate_dict_keys
|
49
|
+
)
|
50
|
+
_type_filters = validator("type_filters", allow_reuse=True)(
|
51
|
+
validate_type_filters
|
52
|
+
)
|
53
|
+
_attribute_filters = validator("attribute_filters", allow_reuse=True)(
|
54
|
+
validate_attribute_filters
|
55
|
+
)
|
56
|
+
|
57
|
+
_name = validator("name", allow_reuse=True)(valstr("name"))
|
58
|
+
|
40
59
|
@validator("zarr_dir")
|
41
60
|
def normalize_zarr_dir(cls, v: Optional[str]) -> Optional[str]:
|
42
61
|
if v is not None:
|
43
62
|
return normalize_url(v)
|
44
63
|
return v
|
45
64
|
|
46
|
-
_name = validator("name", allow_reuse=True)(valstr("name"))
|
47
|
-
|
48
65
|
|
49
66
|
class DatasetReadV2(BaseModel):
|
50
67
|
|
@@ -59,24 +76,37 @@ class DatasetReadV2(BaseModel):
|
|
59
76
|
timestamp_created: datetime
|
60
77
|
|
61
78
|
zarr_dir: str
|
62
|
-
|
79
|
+
type_filters: dict[str, bool]
|
80
|
+
attribute_filters: AttributeFiltersType
|
63
81
|
|
64
82
|
|
65
83
|
class DatasetUpdateV2(BaseModel, extra=Extra.forbid):
|
66
84
|
|
67
85
|
name: Optional[str]
|
68
86
|
zarr_dir: Optional[str]
|
69
|
-
|
87
|
+
type_filters: Optional[dict[str, bool]]
|
88
|
+
attribute_filters: Optional[dict[str, list[Any]]]
|
70
89
|
|
71
90
|
# Validators
|
91
|
+
|
92
|
+
_dict_keys = root_validator(pre=True, allow_reuse=True)(
|
93
|
+
root_validate_dict_keys
|
94
|
+
)
|
95
|
+
_type_filters = validator("type_filters", allow_reuse=True)(
|
96
|
+
validate_type_filters
|
97
|
+
)
|
98
|
+
_attribute_filters = validator("attribute_filters", allow_reuse=True)(
|
99
|
+
validate_attribute_filters
|
100
|
+
)
|
101
|
+
|
102
|
+
_name = validator("name", allow_reuse=True)(valstr("name"))
|
103
|
+
|
72
104
|
@validator("zarr_dir")
|
73
105
|
def normalize_zarr_dir(cls, v: Optional[str]) -> Optional[str]:
|
74
106
|
if v is not None:
|
75
107
|
return normalize_url(v)
|
76
108
|
return v
|
77
109
|
|
78
|
-
_name = validator("name", allow_reuse=True)(valstr("name"))
|
79
|
-
|
80
110
|
|
81
111
|
class DatasetImportV2(BaseModel, extra=Extra.forbid):
|
82
112
|
"""
|
@@ -86,15 +116,29 @@ class DatasetImportV2(BaseModel, extra=Extra.forbid):
|
|
86
116
|
name:
|
87
117
|
zarr_dir:
|
88
118
|
images:
|
89
|
-
|
119
|
+
type_filters:
|
120
|
+
attribute_filters:
|
90
121
|
"""
|
91
122
|
|
92
123
|
name: str
|
93
124
|
zarr_dir: str
|
94
125
|
images: list[SingleImage] = Field(default_factory=list)
|
95
|
-
|
126
|
+
|
127
|
+
type_filters: dict[str, bool] = Field(default_factory=dict)
|
128
|
+
attribute_filters: AttributeFiltersType = Field(default_factory=dict)
|
96
129
|
|
97
130
|
# Validators
|
131
|
+
|
132
|
+
_dict_keys = root_validator(pre=True, allow_reuse=True)(
|
133
|
+
root_validate_dict_keys
|
134
|
+
)
|
135
|
+
_type_filters = validator("type_filters", allow_reuse=True)(
|
136
|
+
validate_type_filters
|
137
|
+
)
|
138
|
+
_attribute_filters = validator("attribute_filters", allow_reuse=True)(
|
139
|
+
validate_attribute_filters
|
140
|
+
)
|
141
|
+
|
98
142
|
@validator("zarr_dir")
|
99
143
|
def normalize_zarr_dir(cls, v: str) -> str:
|
100
144
|
return normalize_url(v)
|
@@ -108,10 +152,12 @@ class DatasetExportV2(BaseModel):
|
|
108
152
|
name:
|
109
153
|
zarr_dir:
|
110
154
|
images:
|
111
|
-
|
155
|
+
type_filters:
|
156
|
+
attribute_filters:
|
112
157
|
"""
|
113
158
|
|
114
159
|
name: str
|
115
160
|
zarr_dir: str
|
116
161
|
images: list[SingleImage]
|
117
|
-
|
162
|
+
type_filters: dict[str, bool]
|
163
|
+
attribute_filters: AttributeFiltersType
|
@@ -13,7 +13,7 @@ from typing import Optional
|
|
13
13
|
from pydantic import BaseModel
|
14
14
|
from pydantic import Extra
|
15
15
|
|
16
|
-
from fractal_server.images import
|
16
|
+
from fractal_server.images.models import AttributeFiltersType
|
17
17
|
|
18
18
|
|
19
19
|
class ProjectDumpV2(BaseModel, extra=Extra.forbid):
|
@@ -39,19 +39,16 @@ class TaskDumpV2(BaseModel):
|
|
39
39
|
|
40
40
|
class WorkflowTaskDumpV2(BaseModel):
|
41
41
|
"""
|
42
|
-
|
43
|
-
|
44
|
-
may still exist in the database after version updates, we are setting
|
45
|
-
`task_id` and `task` to `Optional` to avoid response-validation errors
|
42
|
+
We do not include 'extra=Extra.forbid' because legacy data may include
|
43
|
+
'input_filters' field and we want to avoid response-validation errors
|
46
44
|
for the endpoints that GET datasets.
|
47
|
-
Ref issue #1783.
|
48
45
|
"""
|
49
46
|
|
50
47
|
id: int
|
51
48
|
workflow_id: int
|
52
49
|
order: Optional[int]
|
53
50
|
|
54
|
-
|
51
|
+
type_filters: dict[str, bool]
|
55
52
|
|
56
53
|
task_id: Optional[int]
|
57
54
|
task: Optional[TaskDumpV2]
|
@@ -71,4 +68,5 @@ class DatasetDumpV2(BaseModel, extra=Extra.forbid):
|
|
71
68
|
timestamp_created: str
|
72
69
|
|
73
70
|
zarr_dir: str
|
74
|
-
|
71
|
+
type_filters: dict[str, bool]
|
72
|
+
attribute_filters: AttributeFiltersType
|
@@ -4,13 +4,18 @@ from typing import Optional
|
|
4
4
|
|
5
5
|
from pydantic import BaseModel
|
6
6
|
from pydantic import Extra
|
7
|
+
from pydantic import Field
|
8
|
+
from pydantic import root_validator
|
7
9
|
from pydantic import validator
|
8
10
|
from pydantic.types import StrictStr
|
9
11
|
|
12
|
+
from .._filter_validators import validate_attribute_filters
|
13
|
+
from .._validators import root_validate_dict_keys
|
10
14
|
from .._validators import valstr
|
11
15
|
from .dumps import DatasetDumpV2
|
12
16
|
from .dumps import ProjectDumpV2
|
13
17
|
from .dumps import WorkflowDumpV2
|
18
|
+
from fractal_server.images.models import AttributeFiltersType
|
14
19
|
|
15
20
|
|
16
21
|
class JobStatusTypeV2(str, Enum):
|
@@ -41,10 +46,18 @@ class JobCreateV2(BaseModel, extra=Extra.forbid):
|
|
41
46
|
slurm_account: Optional[StrictStr] = None
|
42
47
|
worker_init: Optional[str]
|
43
48
|
|
49
|
+
attribute_filters: AttributeFiltersType = Field(default_factory=dict)
|
50
|
+
|
44
51
|
# Validators
|
45
52
|
_worker_init = validator("worker_init", allow_reuse=True)(
|
46
53
|
valstr("worker_init")
|
47
54
|
)
|
55
|
+
_dict_keys = root_validator(pre=True, allow_reuse=True)(
|
56
|
+
root_validate_dict_keys
|
57
|
+
)
|
58
|
+
_attribute_filters = validator("attribute_filters", allow_reuse=True)(
|
59
|
+
validate_attribute_filters
|
60
|
+
)
|
48
61
|
|
49
62
|
@validator("first_task_index", always=True)
|
50
63
|
def first_task_index_non_negative(cls, v, values):
|
@@ -99,6 +112,7 @@ class JobReadV2(BaseModel):
|
|
99
112
|
first_task_index: Optional[int]
|
100
113
|
last_task_index: Optional[int]
|
101
114
|
worker_init: Optional[str]
|
115
|
+
attribute_filters: AttributeFiltersType
|
102
116
|
|
103
117
|
|
104
118
|
class JobUpdateV2(BaseModel, extra=Extra.forbid):
|
@@ -10,7 +10,7 @@ from pydantic import root_validator
|
|
10
10
|
from pydantic import validator
|
11
11
|
|
12
12
|
from fractal_server.app.schemas._validators import val_unique_list
|
13
|
-
from fractal_server.app.schemas._validators import
|
13
|
+
from fractal_server.app.schemas._validators import valdict_keys
|
14
14
|
from fractal_server.app.schemas._validators import valstr
|
15
15
|
from fractal_server.string_tools import validate_cmd
|
16
16
|
|
@@ -66,25 +66,25 @@ class TaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
66
66
|
_version = validator("version", allow_reuse=True)(valstr("version"))
|
67
67
|
|
68
68
|
_meta_non_parallel = validator("meta_non_parallel", allow_reuse=True)(
|
69
|
-
|
69
|
+
valdict_keys("meta_non_parallel")
|
70
70
|
)
|
71
71
|
_meta_parallel = validator("meta_parallel", allow_reuse=True)(
|
72
|
-
|
72
|
+
valdict_keys("meta_parallel")
|
73
73
|
)
|
74
74
|
_args_schema_non_parallel = validator(
|
75
75
|
"args_schema_non_parallel", allow_reuse=True
|
76
|
-
)(
|
76
|
+
)(valdict_keys("args_schema_non_parallel"))
|
77
77
|
_args_schema_parallel = validator(
|
78
78
|
"args_schema_parallel", allow_reuse=True
|
79
|
-
)(
|
79
|
+
)(valdict_keys("args_schema_parallel"))
|
80
80
|
_args_schema_version = validator("args_schema_version", allow_reuse=True)(
|
81
81
|
valstr("args_schema_version")
|
82
82
|
)
|
83
83
|
_input_types = validator("input_types", allow_reuse=True)(
|
84
|
-
|
84
|
+
valdict_keys("input_types")
|
85
85
|
)
|
86
86
|
_output_types = validator("output_types", allow_reuse=True)(
|
87
|
-
|
87
|
+
valdict_keys("output_types")
|
88
88
|
)
|
89
89
|
|
90
90
|
_category = validator("category", allow_reuse=True)(
|
@@ -158,10 +158,10 @@ class TaskUpdateV2(BaseModel, extra=Extra.forbid):
|
|
158
158
|
"command_non_parallel", allow_reuse=True
|
159
159
|
)(valstr("command_non_parallel"))
|
160
160
|
_input_types = validator("input_types", allow_reuse=True)(
|
161
|
-
|
161
|
+
valdict_keys("input_types")
|
162
162
|
)
|
163
163
|
_output_types = validator("output_types", allow_reuse=True)(
|
164
|
-
|
164
|
+
valdict_keys("output_types")
|
165
165
|
)
|
166
166
|
|
167
167
|
_category = validator("category", allow_reuse=True)(
|
@@ -8,7 +8,7 @@ from pydantic import Field
|
|
8
8
|
from pydantic import validator
|
9
9
|
|
10
10
|
from .._validators import val_absolute_path
|
11
|
-
from .._validators import
|
11
|
+
from .._validators import valdict_keys
|
12
12
|
from .._validators import valstr
|
13
13
|
from .task import TaskReadV2
|
14
14
|
|
@@ -57,7 +57,7 @@ class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
|
|
57
57
|
)
|
58
58
|
_pinned_package_versions = validator(
|
59
59
|
"pinned_package_versions", allow_reuse=True
|
60
|
-
)(
|
60
|
+
)(valdict_keys("pinned_package_versions"))
|
61
61
|
_pip_extras = validator("pip_extras", allow_reuse=True)(
|
62
62
|
valstr("pip_extras")
|
63
63
|
)
|
@@ -6,14 +6,16 @@ from typing import Union
|
|
6
6
|
from pydantic import BaseModel
|
7
7
|
from pydantic import Extra
|
8
8
|
from pydantic import Field
|
9
|
+
from pydantic import root_validator
|
9
10
|
from pydantic import validator
|
10
11
|
|
11
|
-
from ..
|
12
|
+
from .._filter_validators import validate_type_filters
|
13
|
+
from .._validators import root_validate_dict_keys
|
14
|
+
from .._validators import valdict_keys
|
12
15
|
from .task import TaskExportV2
|
13
16
|
from .task import TaskImportV2
|
14
17
|
from .task import TaskImportV2Legacy
|
15
18
|
from .task import TaskReadV2
|
16
|
-
from fractal_server.images import Filters
|
17
19
|
|
18
20
|
RESERVED_ARGUMENTS = {"zarr_dir", "zarr_url", "zarr_urls", "init_args"}
|
19
21
|
|
@@ -43,21 +45,28 @@ class WorkflowTaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
43
45
|
meta_parallel: Optional[dict[str, Any]]
|
44
46
|
args_non_parallel: Optional[dict[str, Any]]
|
45
47
|
args_parallel: Optional[dict[str, Any]]
|
46
|
-
|
48
|
+
type_filters: dict[str, bool] = Field(default_factory=dict)
|
47
49
|
|
48
50
|
# Validators
|
51
|
+
_dict_keys = root_validator(pre=True, allow_reuse=True)(
|
52
|
+
root_validate_dict_keys
|
53
|
+
)
|
54
|
+
_type_filters = validator("type_filters", allow_reuse=True)(
|
55
|
+
validate_type_filters
|
56
|
+
)
|
57
|
+
|
49
58
|
_meta_non_parallel = validator("meta_non_parallel", allow_reuse=True)(
|
50
|
-
|
59
|
+
valdict_keys("meta_non_parallel")
|
51
60
|
)
|
52
61
|
_meta_parallel = validator("meta_parallel", allow_reuse=True)(
|
53
|
-
|
62
|
+
valdict_keys("meta_parallel")
|
54
63
|
)
|
55
64
|
|
56
65
|
@validator("args_non_parallel")
|
57
66
|
def validate_args_non_parallel(cls, value):
|
58
67
|
if value is None:
|
59
68
|
return
|
60
|
-
|
69
|
+
valdict_keys("args_non_parallel")(value)
|
61
70
|
args_keys = set(value.keys())
|
62
71
|
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
63
72
|
if intersect_keys:
|
@@ -71,7 +80,7 @@ class WorkflowTaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
71
80
|
def validate_args_parallel(cls, value):
|
72
81
|
if value is None:
|
73
82
|
return
|
74
|
-
|
83
|
+
valdict_keys("args_parallel")(value)
|
75
84
|
args_keys = set(value.keys())
|
76
85
|
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
77
86
|
if intersect_keys:
|
@@ -101,7 +110,7 @@ class WorkflowTaskReadV2(BaseModel):
|
|
101
110
|
args_non_parallel: Optional[dict[str, Any]]
|
102
111
|
args_parallel: Optional[dict[str, Any]]
|
103
112
|
|
104
|
-
|
113
|
+
type_filters: dict[str, bool]
|
105
114
|
|
106
115
|
task_type: str
|
107
116
|
task_id: int
|
@@ -118,21 +127,28 @@ class WorkflowTaskUpdateV2(BaseModel, extra=Extra.forbid):
|
|
118
127
|
meta_parallel: Optional[dict[str, Any]]
|
119
128
|
args_non_parallel: Optional[dict[str, Any]]
|
120
129
|
args_parallel: Optional[dict[str, Any]]
|
121
|
-
|
130
|
+
type_filters: Optional[dict[str, bool]]
|
122
131
|
|
123
132
|
# Validators
|
133
|
+
_dict_keys = root_validator(pre=True, allow_reuse=True)(
|
134
|
+
root_validate_dict_keys
|
135
|
+
)
|
136
|
+
_type_filters = validator("type_filters", allow_reuse=True)(
|
137
|
+
validate_type_filters
|
138
|
+
)
|
139
|
+
|
124
140
|
_meta_non_parallel = validator("meta_non_parallel", allow_reuse=True)(
|
125
|
-
|
141
|
+
valdict_keys("meta_non_parallel")
|
126
142
|
)
|
127
143
|
_meta_parallel = validator("meta_parallel", allow_reuse=True)(
|
128
|
-
|
144
|
+
valdict_keys("meta_parallel")
|
129
145
|
)
|
130
146
|
|
131
147
|
@validator("args_non_parallel")
|
132
148
|
def validate_args_non_parallel(cls, value):
|
133
149
|
if value is None:
|
134
150
|
return
|
135
|
-
|
151
|
+
valdict_keys("args_non_parallel")(value)
|
136
152
|
args_keys = set(value.keys())
|
137
153
|
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
138
154
|
if intersect_keys:
|
@@ -146,7 +162,7 @@ class WorkflowTaskUpdateV2(BaseModel, extra=Extra.forbid):
|
|
146
162
|
def validate_args_parallel(cls, value):
|
147
163
|
if value is None:
|
148
164
|
return
|
149
|
-
|
165
|
+
valdict_keys("args_parallel")(value)
|
150
166
|
args_keys = set(value.keys())
|
151
167
|
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
152
168
|
if intersect_keys:
|
@@ -164,21 +180,28 @@ class WorkflowTaskImportV2(BaseModel, extra=Extra.forbid):
|
|
164
180
|
args_non_parallel: Optional[dict[str, Any]] = None
|
165
181
|
args_parallel: Optional[dict[str, Any]] = None
|
166
182
|
|
167
|
-
|
183
|
+
type_filters: Optional[dict[str, bool]] = None
|
168
184
|
|
169
185
|
task: Union[TaskImportV2, TaskImportV2Legacy]
|
170
186
|
|
187
|
+
_dict_keys = root_validator(pre=True, allow_reuse=True)(
|
188
|
+
root_validate_dict_keys
|
189
|
+
)
|
190
|
+
_type_filters = validator("type_filters", allow_reuse=True)(
|
191
|
+
validate_type_filters
|
192
|
+
)
|
193
|
+
|
171
194
|
_meta_non_parallel = validator("meta_non_parallel", allow_reuse=True)(
|
172
|
-
|
195
|
+
valdict_keys("meta_non_parallel")
|
173
196
|
)
|
174
197
|
_meta_parallel = validator("meta_parallel", allow_reuse=True)(
|
175
|
-
|
198
|
+
valdict_keys("meta_parallel")
|
176
199
|
)
|
177
200
|
_args_non_parallel = validator("args_non_parallel", allow_reuse=True)(
|
178
|
-
|
201
|
+
valdict_keys("args_non_parallel")
|
179
202
|
)
|
180
203
|
_args_parallel = validator("args_parallel", allow_reuse=True)(
|
181
|
-
|
204
|
+
valdict_keys("args_parallel")
|
182
205
|
)
|
183
206
|
|
184
207
|
|
@@ -188,6 +211,6 @@ class WorkflowTaskExportV2(BaseModel):
|
|
188
211
|
meta_parallel: Optional[dict[str, Any]] = None
|
189
212
|
args_non_parallel: Optional[dict[str, Any]] = None
|
190
213
|
args_parallel: Optional[dict[str, Any]] = None
|
191
|
-
|
214
|
+
type_filters: dict[str, bool] = Field(default_factory=dict)
|
192
215
|
|
193
216
|
task: TaskExportV2
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from sqlalchemy.orm.attributes import flag_modified
|
4
|
+
from sqlmodel import select
|
5
|
+
|
6
|
+
from fractal_server.app.db import get_sync_db
|
7
|
+
from fractal_server.app.models import DatasetV2
|
8
|
+
from fractal_server.app.models import JobV2
|
9
|
+
from fractal_server.app.models import WorkflowTaskV2
|
10
|
+
|
11
|
+
logger = logging.getLogger("fix_db")
|
12
|
+
logger.setLevel(logging.INFO)
|
13
|
+
|
14
|
+
|
15
|
+
def fix_db():
|
16
|
+
|
17
|
+
logger.info("START execution of fix_db function")
|
18
|
+
|
19
|
+
with next(get_sync_db()) as db:
|
20
|
+
|
21
|
+
# DatasetV2.filters
|
22
|
+
# DatasetV2.history[].workflowtask.input_filters
|
23
|
+
stm = select(DatasetV2).order_by(DatasetV2.id)
|
24
|
+
datasets = db.execute(stm).scalars().all()
|
25
|
+
for ds in datasets:
|
26
|
+
ds.attribute_filters = ds.filters["attributes"]
|
27
|
+
ds.type_filters = ds.filters["types"]
|
28
|
+
ds.filters = None
|
29
|
+
for i, h in enumerate(ds.history):
|
30
|
+
ds.history[i]["workflowtask"]["type_filters"] = h[
|
31
|
+
"workflowtask"
|
32
|
+
]["input_filters"]["types"]
|
33
|
+
flag_modified(ds, "history")
|
34
|
+
db.add(ds)
|
35
|
+
logger.info(f"Fixed filters in DatasetV2[{ds.id}]")
|
36
|
+
|
37
|
+
# WorkflowTaskV2.input_filters
|
38
|
+
stm = select(WorkflowTaskV2).order_by(WorkflowTaskV2.id)
|
39
|
+
wftasks = db.execute(stm).scalars().all()
|
40
|
+
for wft in wftasks:
|
41
|
+
wft.type_filters = wft.input_filters["types"]
|
42
|
+
if wft.input_filters["attributes"]:
|
43
|
+
logger.warning(
|
44
|
+
f"Removing WorkflowTaskV2[{wft.id}].input_filters"
|
45
|
+
f"['attributes'] = {wft.input_filters['attributes']}"
|
46
|
+
)
|
47
|
+
wft.input_filters = None
|
48
|
+
flag_modified(wft, "input_filters")
|
49
|
+
db.add(wft)
|
50
|
+
logger.info(f"Fixed filters in WorkflowTaskV2[{wft.id}]")
|
51
|
+
|
52
|
+
# JOBS V2
|
53
|
+
stm = select(JobV2).order_by(JobV2.id)
|
54
|
+
jobs = db.execute(stm).scalars().all()
|
55
|
+
for job in jobs:
|
56
|
+
job.dataset_dump["type_filters"] = job.dataset_dump["filters"][
|
57
|
+
"types"
|
58
|
+
]
|
59
|
+
job.dataset_dump["attribute_filters"] = job.dataset_dump[
|
60
|
+
"filters"
|
61
|
+
]["attributes"]
|
62
|
+
job.dataset_dump.pop("filters")
|
63
|
+
flag_modified(job, "dataset_dump")
|
64
|
+
logger.info(f"Fixed filters in JobV2[{job.id}].datasetdump")
|
65
|
+
|
66
|
+
db.commit()
|
67
|
+
logger.info("Changes committed.")
|