fractal-server 2.12.1__py3-none-any.whl → 2.13.0__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/security.py +9 -12
- fractal_server/app/models/v2/dataset.py +2 -2
- fractal_server/app/models/v2/job.py +11 -9
- fractal_server/app/models/v2/task.py +2 -3
- fractal_server/app/models/v2/task_group.py +6 -2
- fractal_server/app/models/v2/workflowtask.py +15 -8
- fractal_server/app/routes/admin/v2/task.py +1 -1
- fractal_server/app/routes/admin/v2/task_group.py +1 -1
- fractal_server/app/routes/api/v2/dataset.py +4 -4
- fractal_server/app/routes/api/v2/images.py +11 -11
- fractal_server/app/routes/api/v2/project.py +2 -2
- fractal_server/app/routes/api/v2/status.py +1 -1
- fractal_server/app/routes/api/v2/submit.py +8 -6
- fractal_server/app/routes/api/v2/task.py +4 -2
- fractal_server/app/routes/api/v2/task_collection.py +3 -2
- fractal_server/app/routes/api/v2/task_group.py +2 -2
- fractal_server/app/routes/api/v2/workflow.py +3 -3
- fractal_server/app/routes/api/v2/workflow_import.py +3 -3
- fractal_server/app/routes/api/v2/workflowtask.py +3 -1
- fractal_server/app/routes/auth/_aux_auth.py +4 -1
- fractal_server/app/routes/auth/current_user.py +3 -5
- fractal_server/app/routes/auth/group.py +1 -1
- fractal_server/app/routes/auth/users.py +2 -4
- fractal_server/app/routes/aux/_runner.py +1 -1
- fractal_server/app/routes/aux/validate_user_settings.py +1 -2
- fractal_server/app/runner/executors/_job_states.py +13 -0
- fractal_server/app/runner/executors/slurm/_slurm_config.py +26 -18
- fractal_server/app/runner/executors/slurm/ssh/__init__.py +0 -3
- fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py +31 -22
- fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py +2 -5
- fractal_server/app/runner/executors/slurm/ssh/executor.py +21 -27
- fractal_server/app/runner/executors/slurm/sudo/__init__.py +0 -3
- fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py +1 -2
- fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py +37 -47
- fractal_server/app/runner/executors/slurm/sudo/executor.py +25 -24
- fractal_server/app/runner/v2/__init__.py +0 -9
- fractal_server/app/runner/v2/_local/_local_config.py +5 -4
- fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py +4 -4
- fractal_server/app/runner/v2/_slurm_sudo/__init__.py +2 -2
- fractal_server/app/runner/v2/deduplicate_list.py +1 -1
- fractal_server/app/runner/v2/runner.py +9 -4
- fractal_server/app/runner/v2/task_interface.py +15 -7
- fractal_server/app/schemas/_filter_validators.py +6 -3
- fractal_server/app/schemas/_validators.py +7 -5
- fractal_server/app/schemas/user.py +23 -18
- fractal_server/app/schemas/user_group.py +25 -11
- fractal_server/app/schemas/user_settings.py +31 -24
- fractal_server/app/schemas/v2/dataset.py +48 -35
- fractal_server/app/schemas/v2/dumps.py +16 -14
- fractal_server/app/schemas/v2/job.py +49 -29
- fractal_server/app/schemas/v2/manifest.py +32 -28
- fractal_server/app/schemas/v2/project.py +18 -8
- fractal_server/app/schemas/v2/task.py +86 -75
- fractal_server/app/schemas/v2/task_collection.py +41 -30
- fractal_server/app/schemas/v2/task_group.py +39 -20
- fractal_server/app/schemas/v2/workflow.py +24 -12
- fractal_server/app/schemas/v2/workflowtask.py +63 -61
- fractal_server/app/security/__init__.py +1 -1
- fractal_server/config.py +32 -25
- fractal_server/images/models.py +18 -12
- fractal_server/main.py +1 -1
- fractal_server/tasks/v2/utils_background.py +1 -1
- fractal_server/tasks/v2/utils_database.py +1 -1
- {fractal_server-2.12.1.dist-info → fractal_server-2.13.0.dist-info}/METADATA +9 -10
- {fractal_server-2.12.1.dist-info → fractal_server-2.13.0.dist-info}/RECORD +69 -72
- fractal_server/app/runner/v2/_local_experimental/__init__.py +0 -121
- fractal_server/app/runner/v2/_local_experimental/_local_config.py +0 -108
- fractal_server/app/runner/v2/_local_experimental/_submit_setup.py +0 -42
- fractal_server/app/runner/v2/_local_experimental/executor.py +0 -157
- {fractal_server-2.12.1.dist-info → fractal_server-2.13.0.dist-info}/LICENSE +0 -0
- {fractal_server-2.12.1.dist-info → fractal_server-2.13.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.12.1.dist-info → fractal_server-2.13.0.dist-info}/entry_points.txt +0 -0
@@ -67,14 +67,14 @@ def get_slurm_config(
|
|
67
67
|
|
68
68
|
# Incorporate slurm_env.default_slurm_config
|
69
69
|
slurm_env = load_slurm_config_file(config_path=config_path)
|
70
|
-
slurm_dict = slurm_env.default_slurm_config.
|
70
|
+
slurm_dict = slurm_env.default_slurm_config.model_dump(
|
71
71
|
exclude_unset=True, exclude={"mem"}
|
72
72
|
)
|
73
73
|
if slurm_env.default_slurm_config.mem:
|
74
74
|
slurm_dict["mem_per_task_MB"] = slurm_env.default_slurm_config.mem
|
75
75
|
|
76
76
|
# Incorporate slurm_env.batching_config
|
77
|
-
for key, value in slurm_env.batching_config.
|
77
|
+
for key, value in slurm_env.batching_config.model_dump().items():
|
78
78
|
slurm_dict[key] = value
|
79
79
|
|
80
80
|
# Incorporate slurm_env.user_local_exports
|
@@ -82,7 +82,7 @@ def get_slurm_config(
|
|
82
82
|
|
83
83
|
logger.debug(
|
84
84
|
"[get_slurm_config] Fractal SLURM configuration file: "
|
85
|
-
f"{slurm_env.
|
85
|
+
f"{slurm_env.model_dump()=}"
|
86
86
|
)
|
87
87
|
|
88
88
|
# GPU-related options
|
@@ -97,7 +97,7 @@ def get_slurm_config(
|
|
97
97
|
needs_gpu = False
|
98
98
|
logger.debug(f"[get_slurm_config] {needs_gpu=}")
|
99
99
|
if needs_gpu:
|
100
|
-
for key, value in slurm_env.gpu_slurm_config.
|
100
|
+
for key, value in slurm_env.gpu_slurm_config.model_dump(
|
101
101
|
exclude_unset=True, exclude={"mem"}
|
102
102
|
).items():
|
103
103
|
slurm_dict[key] = value
|
@@ -21,7 +21,7 @@ from typing import Optional
|
|
21
21
|
|
22
22
|
from ....models.v2 import DatasetV2
|
23
23
|
from ....models.v2 import WorkflowV2
|
24
|
-
from ...executors.slurm.sudo.executor import
|
24
|
+
from ...executors.slurm.sudo.executor import FractalSlurmSudoExecutor
|
25
25
|
from ...set_start_and_last_task_index import set_start_and_last_task_index
|
26
26
|
from ..runner import execute_tasks_v2
|
27
27
|
from ._submit_setup import _slurm_submit_setup
|
@@ -64,7 +64,7 @@ def process_workflow(
|
|
64
64
|
if isinstance(worker_init, str):
|
65
65
|
worker_init = worker_init.split("\n")
|
66
66
|
|
67
|
-
with
|
67
|
+
with FractalSlurmSudoExecutor(
|
68
68
|
debug=True,
|
69
69
|
keep_logs=True,
|
70
70
|
slurm_user=slurm_user,
|
@@ -16,7 +16,7 @@ def deduplicate_list(
|
|
16
16
|
new_list_dict = []
|
17
17
|
new_list_objs = []
|
18
18
|
for this_obj in this_list:
|
19
|
-
this_dict = this_obj.
|
19
|
+
this_dict = this_obj.model_dump()
|
20
20
|
if this_dict not in new_list_dict:
|
21
21
|
new_list_dict.append(this_dict)
|
22
22
|
new_list_objs.append(this_obj)
|
@@ -75,10 +75,13 @@ def execute_tasks_v2(
|
|
75
75
|
with next(get_sync_db()) as db:
|
76
76
|
db_dataset = db.get(DatasetV2, dataset.id)
|
77
77
|
new_history_item = _DatasetHistoryItemV2(
|
78
|
-
workflowtask=
|
78
|
+
workflowtask=dict(
|
79
|
+
**wftask.model_dump(exclude={"task"}),
|
80
|
+
task=wftask.task.model_dump(),
|
81
|
+
),
|
79
82
|
status=WorkflowTaskStatusTypeV2.SUBMITTED,
|
80
83
|
parallelization=dict(), # FIXME: re-include parallelization
|
81
|
-
).
|
84
|
+
).model_dump()
|
82
85
|
db_dataset.history.append(new_history_item)
|
83
86
|
flag_modified(db_dataset, "history")
|
84
87
|
db.merge(db_dataset)
|
@@ -132,7 +135,9 @@ def execute_tasks_v2(
|
|
132
135
|
and current_task_output.image_list_removals == []
|
133
136
|
):
|
134
137
|
current_task_output = TaskOutput(
|
135
|
-
**current_task_output.
|
138
|
+
**current_task_output.model_dump(
|
139
|
+
exclude={"image_list_updates"}
|
140
|
+
),
|
136
141
|
image_list_updates=[
|
137
142
|
dict(zarr_url=img["zarr_url"]) for img in filtered_images
|
138
143
|
],
|
@@ -141,7 +146,7 @@ def execute_tasks_v2(
|
|
141
146
|
# Update image list
|
142
147
|
current_task_output.check_zarr_urls_are_unique()
|
143
148
|
for image_obj in current_task_output.image_list_updates:
|
144
|
-
image = image_obj.
|
149
|
+
image = image_obj.model_dump()
|
145
150
|
# Edit existing image
|
146
151
|
tmp_image_paths = [img["zarr_url"] for img in tmp_images]
|
147
152
|
if image["zarr_url"] in tmp_image_paths:
|
@@ -1,15 +1,17 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
3
|
from pydantic import BaseModel
|
4
|
-
from pydantic import
|
4
|
+
from pydantic import ConfigDict
|
5
5
|
from pydantic import Field
|
6
|
-
from pydantic import
|
6
|
+
from pydantic import field_validator
|
7
7
|
|
8
8
|
from ....images import SingleImageTaskOutput
|
9
9
|
from fractal_server.urls import normalize_url
|
10
10
|
|
11
11
|
|
12
|
-
class TaskOutput(BaseModel
|
12
|
+
class TaskOutput(BaseModel):
|
13
|
+
|
14
|
+
model_config = ConfigDict(extra="forbid")
|
13
15
|
|
14
16
|
image_list_updates: list[SingleImageTaskOutput] = Field(
|
15
17
|
default_factory=list
|
@@ -35,21 +37,27 @@ class TaskOutput(BaseModel, extra=Extra.forbid):
|
|
35
37
|
msg = f"{msg}\n{duplicate}"
|
36
38
|
raise ValueError(msg)
|
37
39
|
|
38
|
-
@
|
40
|
+
@field_validator("image_list_removals")
|
41
|
+
@classmethod
|
39
42
|
def normalize_paths(cls, v: list[str]) -> list[str]:
|
40
43
|
return [normalize_url(zarr_url) for zarr_url in v]
|
41
44
|
|
42
45
|
|
43
|
-
class InitArgsModel(BaseModel
|
46
|
+
class InitArgsModel(BaseModel):
|
47
|
+
|
48
|
+
model_config = ConfigDict(extra="forbid")
|
44
49
|
|
45
50
|
zarr_url: str
|
46
51
|
init_args: dict[str, Any] = Field(default_factory=dict)
|
47
52
|
|
48
|
-
@
|
53
|
+
@field_validator("zarr_url")
|
54
|
+
@classmethod
|
49
55
|
def normalize_path(cls, v: str) -> str:
|
50
56
|
return normalize_url(v)
|
51
57
|
|
52
58
|
|
53
|
-
class InitTaskOutput(BaseModel
|
59
|
+
class InitTaskOutput(BaseModel):
|
60
|
+
|
61
|
+
model_config = ConfigDict(extra="forbid")
|
54
62
|
|
55
63
|
parallelization_list: list[InitArgsModel] = Field(default_factory=list)
|
@@ -5,22 +5,25 @@ from fractal_server.images.models import AttributeFiltersType
|
|
5
5
|
|
6
6
|
|
7
7
|
def validate_type_filters(
|
8
|
-
type_filters: Optional[dict[str, bool]]
|
8
|
+
cls, type_filters: Optional[dict[str, bool]]
|
9
9
|
) -> dict[str, bool]:
|
10
10
|
if type_filters is None:
|
11
11
|
raise ValueError("'type_filters' cannot be 'None'.")
|
12
12
|
|
13
|
-
type_filters = valdict_keys("type_filters")(type_filters)
|
13
|
+
type_filters = valdict_keys("type_filters")(cls, type_filters)
|
14
14
|
return type_filters
|
15
15
|
|
16
16
|
|
17
17
|
def validate_attribute_filters(
|
18
|
+
cls,
|
18
19
|
attribute_filters: Optional[AttributeFiltersType],
|
19
20
|
) -> AttributeFiltersType:
|
20
21
|
if attribute_filters is None:
|
21
22
|
raise ValueError("'attribute_filters' cannot be 'None'.")
|
22
23
|
|
23
|
-
attribute_filters = valdict_keys("attribute_filters")(
|
24
|
+
attribute_filters = valdict_keys("attribute_filters")(
|
25
|
+
cls, attribute_filters
|
26
|
+
)
|
24
27
|
for key, values in attribute_filters.items():
|
25
28
|
if values == []:
|
26
29
|
raise ValueError(
|
@@ -11,7 +11,7 @@ def valstr(attribute: str, accept_none: bool = False):
|
|
11
11
|
If `accept_none`, the validator also accepts `None`.
|
12
12
|
"""
|
13
13
|
|
14
|
-
def val(string: Optional[str]) -> Optional[str]:
|
14
|
+
def val(cls, string: Optional[str]) -> Optional[str]:
|
15
15
|
if string is None:
|
16
16
|
if accept_none:
|
17
17
|
return string
|
@@ -28,14 +28,16 @@ def valstr(attribute: str, accept_none: bool = False):
|
|
28
28
|
|
29
29
|
|
30
30
|
def valdict_keys(attribute: str):
|
31
|
-
def val(d: Optional[dict[str, Any]]) -> Optional[dict[str, Any]]:
|
31
|
+
def val(cls, 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
|
34
34
|
identical keys.
|
35
35
|
"""
|
36
36
|
if d is not None:
|
37
37
|
old_keys = list(d.keys())
|
38
|
-
new_keys = [
|
38
|
+
new_keys = [
|
39
|
+
valstr(f"{attribute}[{key}]")(cls, key) for key in old_keys
|
40
|
+
]
|
39
41
|
if len(new_keys) != len(set(new_keys)):
|
40
42
|
raise ValueError(
|
41
43
|
f"Dictionary contains multiple identical keys: '{d}'."
|
@@ -53,7 +55,7 @@ def val_absolute_path(attribute: str, accept_none: bool = False):
|
|
53
55
|
Check that a string attribute is an absolute path
|
54
56
|
"""
|
55
57
|
|
56
|
-
def val(string: Optional[str]) -> Optional[str]:
|
58
|
+
def val(cls, string: Optional[str]) -> Optional[str]:
|
57
59
|
if string is None:
|
58
60
|
if accept_none:
|
59
61
|
return string
|
@@ -75,7 +77,7 @@ def val_absolute_path(attribute: str, accept_none: bool = False):
|
|
75
77
|
|
76
78
|
|
77
79
|
def val_unique_list(attribute: str):
|
78
|
-
def val(must_be_unique: Optional[list]) -> Optional[list]:
|
80
|
+
def val(cls, must_be_unique: Optional[list]) -> Optional[list]:
|
79
81
|
if must_be_unique is not None:
|
80
82
|
if len(set(must_be_unique)) != len(must_be_unique):
|
81
83
|
raise ValueError(f"`{attribute}` list has repetitions")
|
@@ -2,9 +2,10 @@ from typing import Optional
|
|
2
2
|
|
3
3
|
from fastapi_users import schemas
|
4
4
|
from pydantic import BaseModel
|
5
|
-
from pydantic import
|
5
|
+
from pydantic import ConfigDict
|
6
6
|
from pydantic import Field
|
7
|
-
from pydantic import
|
7
|
+
from pydantic import field_validator
|
8
|
+
from pydantic import ValidationInfo
|
8
9
|
|
9
10
|
from ._validators import val_unique_list
|
10
11
|
from ._validators import valstr
|
@@ -41,12 +42,12 @@ class UserRead(schemas.BaseUser[int]):
|
|
41
42
|
username:
|
42
43
|
"""
|
43
44
|
|
44
|
-
username: Optional[str]
|
45
|
+
username: Optional[str] = None
|
45
46
|
group_ids_names: Optional[list[tuple[int, str]]] = None
|
46
47
|
oauth_accounts: list[OAuthAccountRead]
|
47
48
|
|
48
49
|
|
49
|
-
class UserUpdate(schemas.BaseUserUpdate
|
50
|
+
class UserUpdate(schemas.BaseUserUpdate):
|
50
51
|
"""
|
51
52
|
Schema for `User` update.
|
52
53
|
|
@@ -54,33 +55,35 @@ class UserUpdate(schemas.BaseUserUpdate, extra=Extra.forbid):
|
|
54
55
|
username:
|
55
56
|
"""
|
56
57
|
|
57
|
-
|
58
|
+
model_config = ConfigDict(extra="forbid")
|
59
|
+
|
60
|
+
username: Optional[str] = None
|
58
61
|
|
59
62
|
# Validators
|
60
|
-
_username =
|
63
|
+
_username = field_validator("username")(classmethod(valstr("username")))
|
61
64
|
|
62
|
-
@
|
65
|
+
@field_validator(
|
63
66
|
"is_active",
|
64
67
|
"is_verified",
|
65
68
|
"is_superuser",
|
66
69
|
"email",
|
67
70
|
"password",
|
68
|
-
always=False,
|
69
71
|
)
|
70
|
-
|
72
|
+
@classmethod
|
73
|
+
def cant_set_none(cls, v, info: ValidationInfo):
|
71
74
|
if v is None:
|
72
|
-
raise ValueError(f"Cannot set {
|
75
|
+
raise ValueError(f"Cannot set {info.field_name}=None")
|
73
76
|
return v
|
74
77
|
|
75
78
|
|
76
|
-
class UserUpdateStrict(BaseModel
|
79
|
+
class UserUpdateStrict(BaseModel):
|
77
80
|
"""
|
78
81
|
Schema for `User` self-editing.
|
79
82
|
|
80
83
|
Attributes:
|
81
84
|
"""
|
82
85
|
|
83
|
-
|
86
|
+
model_config = ConfigDict(extra="forbid")
|
84
87
|
|
85
88
|
|
86
89
|
class UserCreate(schemas.BaseUserCreate):
|
@@ -91,21 +94,23 @@ class UserCreate(schemas.BaseUserCreate):
|
|
91
94
|
username:
|
92
95
|
"""
|
93
96
|
|
94
|
-
username: Optional[str]
|
97
|
+
username: Optional[str] = None
|
95
98
|
|
96
99
|
# Validators
|
97
100
|
|
98
|
-
_username =
|
101
|
+
_username = field_validator("username")(classmethod(valstr("username")))
|
99
102
|
|
100
103
|
|
101
|
-
class UserUpdateGroups(BaseModel
|
104
|
+
class UserUpdateGroups(BaseModel):
|
102
105
|
"""
|
103
106
|
Schema for `POST /auth/users/{user_id}/set-groups/`
|
104
107
|
|
105
108
|
"""
|
106
109
|
|
107
|
-
|
110
|
+
model_config = ConfigDict(extra="forbid")
|
111
|
+
|
112
|
+
group_ids: list[int] = Field(min_length=1)
|
108
113
|
|
109
|
-
_group_ids =
|
110
|
-
val_unique_list("group_ids")
|
114
|
+
_group_ids = field_validator("group_ids")(
|
115
|
+
classmethod(val_unique_list("group_ids"))
|
111
116
|
)
|
@@ -2,9 +2,11 @@ from datetime import datetime
|
|
2
2
|
from typing import Optional
|
3
3
|
|
4
4
|
from pydantic import BaseModel
|
5
|
-
from pydantic import
|
5
|
+
from pydantic import ConfigDict
|
6
6
|
from pydantic import Field
|
7
|
-
from pydantic import
|
7
|
+
from pydantic import field_serializer
|
8
|
+
from pydantic import field_validator
|
9
|
+
from pydantic.types import AwareDatetime
|
8
10
|
|
9
11
|
from ._validators import val_absolute_path
|
10
12
|
from ._validators import val_unique_list
|
@@ -32,12 +34,16 @@ class UserGroupRead(BaseModel):
|
|
32
34
|
|
33
35
|
id: int
|
34
36
|
name: str
|
35
|
-
timestamp_created:
|
37
|
+
timestamp_created: AwareDatetime
|
36
38
|
user_ids: Optional[list[int]] = None
|
37
39
|
viewer_paths: list[str]
|
38
40
|
|
41
|
+
@field_serializer("timestamp_created")
|
42
|
+
def serialize_datetime(v: datetime) -> str:
|
43
|
+
return v.isoformat()
|
39
44
|
|
40
|
-
|
45
|
+
|
46
|
+
class UserGroupCreate(BaseModel):
|
41
47
|
"""
|
42
48
|
Schema for `UserGroup` creation
|
43
49
|
|
@@ -45,27 +51,35 @@ class UserGroupCreate(BaseModel, extra=Extra.forbid):
|
|
45
51
|
name: Group name
|
46
52
|
"""
|
47
53
|
|
54
|
+
model_config = ConfigDict(extra="forbid")
|
55
|
+
|
48
56
|
name: str
|
49
57
|
viewer_paths: list[str] = Field(default_factory=list)
|
50
58
|
|
51
|
-
@
|
59
|
+
@field_validator("viewer_paths")
|
60
|
+
@classmethod
|
52
61
|
def viewer_paths_validator(cls, value):
|
53
62
|
for i, path in enumerate(value):
|
54
|
-
value[i] = val_absolute_path(f"viewer_paths[{i}]")(path)
|
55
|
-
value = val_unique_list("viewer_paths")(value)
|
63
|
+
value[i] = val_absolute_path(f"viewer_paths[{i}]")(cls, path)
|
64
|
+
value = val_unique_list("viewer_paths")(cls, value)
|
56
65
|
return value
|
57
66
|
|
58
67
|
|
59
|
-
class UserGroupUpdate(BaseModel
|
68
|
+
class UserGroupUpdate(BaseModel):
|
60
69
|
"""
|
61
70
|
Schema for `UserGroup` update
|
62
71
|
"""
|
63
72
|
|
73
|
+
model_config = ConfigDict(extra="forbid")
|
74
|
+
|
64
75
|
viewer_paths: Optional[list[str]] = None
|
65
76
|
|
66
|
-
@
|
77
|
+
@field_validator("viewer_paths")
|
78
|
+
@classmethod
|
67
79
|
def viewer_paths_validator(cls, value):
|
80
|
+
if value is None:
|
81
|
+
raise ValueError("Cannot set `viewer_paths=None`.")
|
68
82
|
for i, path in enumerate(value):
|
69
|
-
value[i] = val_absolute_path(f"viewer_paths[{i}]")(path)
|
70
|
-
value = val_unique_list("viewer_paths")(value)
|
83
|
+
value[i] = val_absolute_path(f"viewer_paths[{i}]")(cls, path)
|
84
|
+
value = val_unique_list("viewer_paths")(cls, value)
|
71
85
|
return value
|
@@ -1,8 +1,8 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
|
3
3
|
from pydantic import BaseModel
|
4
|
-
from pydantic import
|
5
|
-
from pydantic import
|
4
|
+
from pydantic import ConfigDict
|
5
|
+
from pydantic import field_validator
|
6
6
|
from pydantic.types import StrictStr
|
7
7
|
|
8
8
|
from ._validators import val_absolute_path
|
@@ -41,11 +41,13 @@ class UserSettingsReadStrict(BaseModel):
|
|
41
41
|
project_dir: Optional[str] = None
|
42
42
|
|
43
43
|
|
44
|
-
class UserSettingsUpdate(BaseModel
|
44
|
+
class UserSettingsUpdate(BaseModel):
|
45
45
|
"""
|
46
46
|
Schema reserved for superusers
|
47
47
|
"""
|
48
48
|
|
49
|
+
model_config = ConfigDict(extra="forbid")
|
50
|
+
|
49
51
|
ssh_host: Optional[str] = None
|
50
52
|
ssh_username: Optional[str] = None
|
51
53
|
ssh_private_key_path: Optional[str] = None
|
@@ -55,46 +57,51 @@ class UserSettingsUpdate(BaseModel, extra=Extra.forbid):
|
|
55
57
|
slurm_accounts: Optional[list[StrictStr]] = None
|
56
58
|
project_dir: Optional[str] = None
|
57
59
|
|
58
|
-
_ssh_host =
|
59
|
-
valstr("ssh_host", accept_none=True)
|
60
|
+
_ssh_host = field_validator("ssh_host")(
|
61
|
+
classmethod(valstr("ssh_host", accept_none=True))
|
62
|
+
)
|
63
|
+
_ssh_username = field_validator("ssh_username")(
|
64
|
+
classmethod(valstr("ssh_username", accept_none=True))
|
60
65
|
)
|
61
|
-
|
62
|
-
|
66
|
+
_ssh_private_key_path = field_validator("ssh_private_key_path")(
|
67
|
+
classmethod(
|
68
|
+
val_absolute_path("ssh_private_key_path", accept_none=True)
|
69
|
+
)
|
63
70
|
)
|
64
|
-
_ssh_private_key_path = validator(
|
65
|
-
"ssh_private_key_path", allow_reuse=True
|
66
|
-
)(val_absolute_path("ssh_private_key_path", accept_none=True))
|
67
71
|
|
68
|
-
_ssh_tasks_dir =
|
69
|
-
val_absolute_path("ssh_tasks_dir", accept_none=True)
|
72
|
+
_ssh_tasks_dir = field_validator("ssh_tasks_dir")(
|
73
|
+
classmethod(val_absolute_path("ssh_tasks_dir", accept_none=True))
|
70
74
|
)
|
71
|
-
_ssh_jobs_dir =
|
72
|
-
val_absolute_path("ssh_jobs_dir", accept_none=True)
|
75
|
+
_ssh_jobs_dir = field_validator("ssh_jobs_dir")(
|
76
|
+
classmethod(val_absolute_path("ssh_jobs_dir", accept_none=True))
|
73
77
|
)
|
74
78
|
|
75
|
-
_slurm_user =
|
76
|
-
valstr("slurm_user", accept_none=True)
|
79
|
+
_slurm_user = field_validator("slurm_user")(
|
80
|
+
classmethod(valstr("slurm_user", accept_none=True))
|
77
81
|
)
|
78
82
|
|
79
|
-
@
|
83
|
+
@field_validator("slurm_accounts")
|
84
|
+
@classmethod
|
80
85
|
def slurm_accounts_validator(cls, value):
|
81
86
|
if value is None:
|
82
87
|
return value
|
83
88
|
for i, item in enumerate(value):
|
84
|
-
value[i] = valstr(f"slurm_accounts[{i}]")(item)
|
85
|
-
return val_unique_list("slurm_accounts")(value)
|
89
|
+
value[i] = valstr(f"slurm_accounts[{i}]")(cls, item)
|
90
|
+
return val_unique_list("slurm_accounts")(cls, value)
|
86
91
|
|
87
|
-
@
|
92
|
+
@field_validator("project_dir")
|
93
|
+
@classmethod
|
88
94
|
def project_dir_validator(cls, value):
|
89
95
|
if value is None:
|
90
96
|
return None
|
91
97
|
validate_cmd(value)
|
92
|
-
return val_absolute_path("project_dir")(value)
|
98
|
+
return val_absolute_path("project_dir")(cls, value)
|
93
99
|
|
94
100
|
|
95
|
-
class UserSettingsUpdateStrict(BaseModel
|
101
|
+
class UserSettingsUpdateStrict(BaseModel):
|
102
|
+
model_config = ConfigDict(extra="forbid")
|
96
103
|
slurm_accounts: Optional[list[StrictStr]] = None
|
97
104
|
|
98
|
-
_slurm_accounts =
|
99
|
-
val_unique_list("slurm_accounts")
|
105
|
+
_slurm_accounts = field_validator("slurm_accounts")(
|
106
|
+
classmethod(val_unique_list("slurm_accounts"))
|
100
107
|
)
|
@@ -3,10 +3,12 @@ from typing import Any
|
|
3
3
|
from typing import Optional
|
4
4
|
|
5
5
|
from pydantic import BaseModel
|
6
|
-
from pydantic import
|
6
|
+
from pydantic import ConfigDict
|
7
7
|
from pydantic import Field
|
8
|
-
from pydantic import
|
9
|
-
from pydantic import
|
8
|
+
from pydantic import field_serializer
|
9
|
+
from pydantic import field_validator
|
10
|
+
from pydantic import model_validator
|
11
|
+
from pydantic.types import AwareDatetime
|
10
12
|
|
11
13
|
from .._filter_validators import validate_attribute_filters
|
12
14
|
from .._filter_validators import validate_type_filters
|
@@ -27,13 +29,14 @@ class _DatasetHistoryItemV2(BaseModel):
|
|
27
29
|
|
28
30
|
workflowtask: WorkflowTaskDumpV2
|
29
31
|
status: WorkflowTaskStatusTypeV2
|
30
|
-
parallelization: Optional[dict]
|
32
|
+
parallelization: Optional[dict] = None
|
31
33
|
|
32
34
|
|
33
35
|
# CRUD
|
34
36
|
|
35
37
|
|
36
|
-
class DatasetCreateV2(BaseModel
|
38
|
+
class DatasetCreateV2(BaseModel):
|
39
|
+
model_config = ConfigDict(extra="forbid")
|
37
40
|
|
38
41
|
name: str
|
39
42
|
|
@@ -44,19 +47,20 @@ class DatasetCreateV2(BaseModel, extra=Extra.forbid):
|
|
44
47
|
|
45
48
|
# Validators
|
46
49
|
|
47
|
-
_dict_keys =
|
48
|
-
root_validate_dict_keys
|
50
|
+
_dict_keys = model_validator(mode="before")(
|
51
|
+
classmethod(root_validate_dict_keys)
|
49
52
|
)
|
50
|
-
_type_filters =
|
51
|
-
validate_type_filters
|
53
|
+
_type_filters = field_validator("type_filters")(
|
54
|
+
classmethod(validate_type_filters)
|
52
55
|
)
|
53
|
-
_attribute_filters =
|
54
|
-
validate_attribute_filters
|
56
|
+
_attribute_filters = field_validator("attribute_filters")(
|
57
|
+
classmethod(validate_attribute_filters)
|
55
58
|
)
|
56
59
|
|
57
|
-
_name =
|
60
|
+
_name = field_validator("name")(classmethod(valstr("name")))
|
58
61
|
|
59
|
-
@
|
62
|
+
@field_validator("zarr_dir")
|
63
|
+
@classmethod
|
60
64
|
def normalize_zarr_dir(cls, v: Optional[str]) -> Optional[str]:
|
61
65
|
if v is not None:
|
62
66
|
return normalize_url(v)
|
@@ -64,7 +68,6 @@ class DatasetCreateV2(BaseModel, extra=Extra.forbid):
|
|
64
68
|
|
65
69
|
|
66
70
|
class DatasetReadV2(BaseModel):
|
67
|
-
|
68
71
|
id: int
|
69
72
|
name: str
|
70
73
|
|
@@ -73,42 +76,48 @@ class DatasetReadV2(BaseModel):
|
|
73
76
|
|
74
77
|
history: list[_DatasetHistoryItemV2]
|
75
78
|
|
76
|
-
timestamp_created:
|
79
|
+
timestamp_created: AwareDatetime
|
77
80
|
|
78
81
|
zarr_dir: str
|
79
82
|
type_filters: dict[str, bool]
|
80
83
|
attribute_filters: AttributeFiltersType
|
81
84
|
|
85
|
+
@field_serializer("timestamp_created")
|
86
|
+
def serialize_datetime(v: datetime) -> str:
|
87
|
+
return v.isoformat()
|
88
|
+
|
82
89
|
|
83
|
-
class DatasetUpdateV2(BaseModel
|
90
|
+
class DatasetUpdateV2(BaseModel):
|
91
|
+
model_config = ConfigDict(extra="forbid")
|
84
92
|
|
85
|
-
name: Optional[str]
|
86
|
-
zarr_dir: Optional[str]
|
87
|
-
type_filters: Optional[dict[str, bool]]
|
88
|
-
attribute_filters: Optional[dict[str, list[Any]]]
|
93
|
+
name: Optional[str] = None
|
94
|
+
zarr_dir: Optional[str] = None
|
95
|
+
type_filters: Optional[dict[str, bool]] = None
|
96
|
+
attribute_filters: Optional[dict[str, list[Any]]] = None
|
89
97
|
|
90
98
|
# Validators
|
91
99
|
|
92
|
-
_dict_keys =
|
93
|
-
root_validate_dict_keys
|
100
|
+
_dict_keys = model_validator(mode="before")(
|
101
|
+
classmethod(root_validate_dict_keys)
|
94
102
|
)
|
95
|
-
_type_filters =
|
96
|
-
validate_type_filters
|
103
|
+
_type_filters = field_validator("type_filters")(
|
104
|
+
classmethod(validate_type_filters)
|
97
105
|
)
|
98
|
-
_attribute_filters =
|
99
|
-
validate_attribute_filters
|
106
|
+
_attribute_filters = field_validator("attribute_filters")(
|
107
|
+
classmethod(validate_attribute_filters)
|
100
108
|
)
|
101
109
|
|
102
|
-
_name =
|
110
|
+
_name = field_validator("name")(classmethod(valstr("name")))
|
103
111
|
|
104
|
-
@
|
112
|
+
@field_validator("zarr_dir")
|
113
|
+
@classmethod
|
105
114
|
def normalize_zarr_dir(cls, v: Optional[str]) -> Optional[str]:
|
106
115
|
if v is not None:
|
107
116
|
return normalize_url(v)
|
108
117
|
return v
|
109
118
|
|
110
119
|
|
111
|
-
class DatasetImportV2(BaseModel
|
120
|
+
class DatasetImportV2(BaseModel):
|
112
121
|
"""
|
113
122
|
Class for `Dataset` import.
|
114
123
|
|
@@ -121,6 +130,8 @@ class DatasetImportV2(BaseModel, extra=Extra.forbid):
|
|
121
130
|
attribute_filters:
|
122
131
|
"""
|
123
132
|
|
133
|
+
model_config = ConfigDict(extra="forbid")
|
134
|
+
|
124
135
|
name: str
|
125
136
|
zarr_dir: str
|
126
137
|
images: list[SingleImage] = Field(default_factory=list)
|
@@ -129,7 +140,8 @@ class DatasetImportV2(BaseModel, extra=Extra.forbid):
|
|
129
140
|
type_filters: dict[str, bool] = Field(default_factory=dict)
|
130
141
|
attribute_filters: AttributeFiltersType = Field(default_factory=dict)
|
131
142
|
|
132
|
-
@
|
143
|
+
@model_validator(mode="before")
|
144
|
+
@classmethod
|
133
145
|
def update_legacy_filters(cls, values: dict):
|
134
146
|
"""
|
135
147
|
Transform legacy filters (created with fractal-server<2.11.0)
|
@@ -159,14 +171,15 @@ class DatasetImportV2(BaseModel, extra=Extra.forbid):
|
|
159
171
|
|
160
172
|
return values
|
161
173
|
|
162
|
-
_type_filters =
|
163
|
-
validate_type_filters
|
174
|
+
_type_filters = field_validator("type_filters")(
|
175
|
+
classmethod(validate_type_filters)
|
164
176
|
)
|
165
|
-
_attribute_filters =
|
166
|
-
validate_attribute_filters
|
177
|
+
_attribute_filters = field_validator("attribute_filters")(
|
178
|
+
classmethod(validate_attribute_filters)
|
167
179
|
)
|
168
180
|
|
169
|
-
@
|
181
|
+
@field_validator("zarr_dir")
|
182
|
+
@classmethod
|
170
183
|
def normalize_zarr_dir(cls, v: str) -> str:
|
171
184
|
return normalize_url(v)
|
172
185
|
|