fractal-server 2.14.3__py3-none-any.whl → 2.14.4__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/job.py +2 -2
- fractal_server/app/routes/api/v2/images.py +4 -20
- fractal_server/app/routes/api/v2/pre_submission_checks.py +2 -2
- fractal_server/app/runner/{run_subprocess.py → executors/slurm_ssh/run_subprocess.py} +11 -6
- fractal_server/app/runner/executors/slurm_ssh/runner.py +11 -56
- fractal_server/app/runner/executors/slurm_ssh/tar_commands.py +65 -0
- fractal_server/app/runner/v2/_local.py +2 -2
- fractal_server/app/runner/v2/_slurm_ssh.py +2 -2
- fractal_server/app/runner/v2/_slurm_sudo.py +2 -2
- fractal_server/app/runner/v2/runner.py +2 -2
- fractal_server/app/runner/v2/task_interface.py +3 -14
- fractal_server/app/schemas/user.py +11 -35
- fractal_server/app/schemas/user_group.py +3 -23
- fractal_server/app/schemas/user_settings.py +17 -43
- fractal_server/app/schemas/v2/dataset.py +10 -50
- fractal_server/app/schemas/v2/job.py +19 -60
- fractal_server/app/schemas/v2/manifest.py +10 -25
- fractal_server/app/schemas/v2/project.py +3 -11
- fractal_server/app/schemas/v2/task.py +36 -106
- fractal_server/app/schemas/v2/task_collection.py +31 -81
- fractal_server/app/schemas/v2/task_group.py +14 -34
- fractal_server/app/schemas/v2/workflow.py +13 -28
- fractal_server/app/schemas/v2/workflowtask.py +18 -126
- fractal_server/config.py +20 -73
- fractal_server/images/models.py +15 -81
- fractal_server/images/tools.py +3 -3
- fractal_server/ssh/_fabric.py +4 -1
- fractal_server/types/__init__.py +87 -0
- fractal_server/types/validators/__init__.py +6 -0
- fractal_server/types/validators/_common_validators.py +42 -0
- fractal_server/types/validators/_filter_validators.py +24 -0
- fractal_server/types/validators/_workflow_task_arguments_validators.py +10 -0
- {fractal_server-2.14.3.dist-info → fractal_server-2.14.4.dist-info}/METADATA +1 -1
- {fractal_server-2.14.3.dist-info → fractal_server-2.14.4.dist-info}/RECORD +38 -36
- fractal_server/app/runner/compress_folder.py +0 -125
- fractal_server/app/runner/extract_archive.py +0 -99
- fractal_server/app/schemas/_filter_validators.py +0 -46
- fractal_server/app/schemas/_validators.py +0 -86
- {fractal_server-2.14.3.dist-info → fractal_server-2.14.4.dist-info}/LICENSE +0 -0
- {fractal_server-2.14.3.dist-info → fractal_server-2.14.4.dist-info}/WHEEL +0 -0
- {fractal_server-2.14.3.dist-info → fractal_server-2.14.4.dist-info}/entry_points.txt +0 -0
@@ -5,40 +5,23 @@ from pydantic import BaseModel
|
|
5
5
|
from pydantic import ConfigDict
|
6
6
|
from pydantic import Field
|
7
7
|
from pydantic import field_serializer
|
8
|
-
from pydantic import field_validator
|
9
|
-
from pydantic import model_validator
|
10
8
|
from pydantic.types import AwareDatetime
|
11
9
|
|
12
|
-
from
|
13
|
-
from .._validators import NonEmptyString
|
14
|
-
from .._validators import root_validate_dict_keys
|
15
|
-
from .project import ProjectReadV2
|
10
|
+
from fractal_server.app.schemas.v2.project import ProjectReadV2
|
16
11
|
from fractal_server.images import SingleImage
|
17
|
-
from fractal_server.
|
18
|
-
from fractal_server.
|
12
|
+
from fractal_server.types import AttributeFilters
|
13
|
+
from fractal_server.types import NonEmptyStr
|
14
|
+
from fractal_server.types import ZarrDirStr
|
19
15
|
|
20
16
|
|
21
17
|
class DatasetCreateV2(BaseModel):
|
22
18
|
model_config = ConfigDict(extra="forbid")
|
23
19
|
|
24
|
-
name:
|
20
|
+
name: NonEmptyStr
|
25
21
|
|
26
|
-
zarr_dir: Optional[
|
22
|
+
zarr_dir: Optional[ZarrDirStr] = None
|
27
23
|
|
28
|
-
attribute_filters:
|
29
|
-
|
30
|
-
# Validators
|
31
|
-
|
32
|
-
_dict_keys = model_validator(mode="before")(
|
33
|
-
classmethod(root_validate_dict_keys)
|
34
|
-
)
|
35
|
-
|
36
|
-
@field_validator("zarr_dir")
|
37
|
-
@classmethod
|
38
|
-
def normalize_zarr_dir(cls, v: Optional[str]) -> Optional[str]:
|
39
|
-
if v is not None:
|
40
|
-
return normalize_url(v)
|
41
|
-
return v
|
24
|
+
attribute_filters: AttributeFilters = Field(default_factory=dict)
|
42
25
|
|
43
26
|
|
44
27
|
class DatasetReadV2(BaseModel):
|
@@ -60,26 +43,8 @@ class DatasetReadV2(BaseModel):
|
|
60
43
|
class DatasetUpdateV2(BaseModel):
|
61
44
|
model_config = ConfigDict(extra="forbid")
|
62
45
|
|
63
|
-
name:
|
64
|
-
zarr_dir: Optional[
|
65
|
-
|
66
|
-
# Validators
|
67
|
-
|
68
|
-
_dict_keys = model_validator(mode="before")(
|
69
|
-
classmethod(root_validate_dict_keys)
|
70
|
-
)
|
71
|
-
|
72
|
-
@field_validator("name")
|
73
|
-
@classmethod
|
74
|
-
def _cant_set_none(cls, v):
|
75
|
-
return cant_set_none(v)
|
76
|
-
|
77
|
-
@field_validator("zarr_dir")
|
78
|
-
@classmethod
|
79
|
-
def normalize_zarr_dir(cls, v: Optional[str]) -> Optional[str]:
|
80
|
-
if v is not None:
|
81
|
-
return normalize_url(v)
|
82
|
-
return v
|
46
|
+
name: NonEmptyStr = None
|
47
|
+
zarr_dir: Optional[ZarrDirStr] = None
|
83
48
|
|
84
49
|
|
85
50
|
class DatasetImportV2(BaseModel):
|
@@ -97,14 +62,9 @@ class DatasetImportV2(BaseModel):
|
|
97
62
|
"""
|
98
63
|
|
99
64
|
name: str
|
100
|
-
zarr_dir:
|
65
|
+
zarr_dir: ZarrDirStr
|
101
66
|
images: list[SingleImage] = Field(default_factory=list)
|
102
67
|
|
103
|
-
@field_validator("zarr_dir")
|
104
|
-
@classmethod
|
105
|
-
def normalize_zarr_dir(cls, v: str) -> str:
|
106
|
-
return normalize_url(v)
|
107
|
-
|
108
68
|
|
109
69
|
class DatasetExportV2(BaseModel):
|
110
70
|
"""
|
@@ -6,21 +6,17 @@ from pydantic import BaseModel
|
|
6
6
|
from pydantic import ConfigDict
|
7
7
|
from pydantic import Field
|
8
8
|
from pydantic import field_serializer
|
9
|
-
from pydantic import field_validator
|
10
9
|
from pydantic import model_validator
|
11
|
-
from pydantic import ValidationInfo
|
12
10
|
from pydantic.types import AwareDatetime
|
11
|
+
from pydantic.types import NonNegativeInt
|
13
12
|
from pydantic.types import StrictStr
|
14
13
|
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
19
|
-
from
|
20
|
-
from .
|
21
|
-
from .dumps import ProjectDumpV2
|
22
|
-
from .dumps import WorkflowDumpV2
|
23
|
-
from fractal_server.images.models import AttributeFiltersType
|
14
|
+
from fractal_server.app.schemas.v2.dumps import DatasetDumpV2
|
15
|
+
from fractal_server.app.schemas.v2.dumps import ProjectDumpV2
|
16
|
+
from fractal_server.app.schemas.v2.dumps import WorkflowDumpV2
|
17
|
+
from fractal_server.types import AttributeFilters
|
18
|
+
from fractal_server.types import NonEmptyStr
|
19
|
+
from fractal_server.types import TypeFilters
|
24
20
|
|
25
21
|
|
26
22
|
class JobStatusTypeV2(str, Enum):
|
@@ -47,64 +43,27 @@ class JobStatusTypeV2(str, Enum):
|
|
47
43
|
class JobCreateV2(BaseModel):
|
48
44
|
model_config = ConfigDict(extra="forbid")
|
49
45
|
|
50
|
-
first_task_index: Optional[
|
51
|
-
last_task_index: Optional[
|
46
|
+
first_task_index: Optional[NonNegativeInt] = None
|
47
|
+
last_task_index: Optional[NonNegativeInt] = None
|
52
48
|
slurm_account: Optional[StrictStr] = None
|
53
|
-
worker_init:
|
54
|
-
|
55
|
-
attribute_filters: AttributeFiltersType = Field(default_factory=dict)
|
56
|
-
type_filters: dict[str, bool] = Field(default_factory=dict)
|
49
|
+
worker_init: NonEmptyStr = None
|
57
50
|
|
58
|
-
|
51
|
+
attribute_filters: AttributeFilters = Field(default_factory=dict)
|
52
|
+
type_filters: TypeFilters = Field(default_factory=dict)
|
59
53
|
|
60
|
-
@
|
54
|
+
@model_validator(mode="before")
|
61
55
|
@classmethod
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
classmethod(root_validate_dict_keys)
|
67
|
-
)
|
68
|
-
_attribute_filters = field_validator("attribute_filters")(
|
69
|
-
classmethod(validate_attribute_filters)
|
70
|
-
)
|
71
|
-
_type_filters = field_validator("type_filters")(
|
72
|
-
classmethod(validate_type_filters)
|
73
|
-
)
|
74
|
-
|
75
|
-
@field_validator("first_task_index")
|
76
|
-
@classmethod
|
77
|
-
def first_task_index_non_negative(cls, v):
|
78
|
-
"""
|
79
|
-
Check that `first_task_index` is non-negative.
|
80
|
-
"""
|
81
|
-
if v is not None and v < 0:
|
82
|
-
raise ValueError(
|
83
|
-
f"first_task_index cannot be negative (given: {v})"
|
84
|
-
)
|
85
|
-
return v
|
86
|
-
|
87
|
-
@field_validator("last_task_index")
|
88
|
-
@classmethod
|
89
|
-
def first_last_task_indices(cls, v, info: ValidationInfo):
|
90
|
-
"""
|
91
|
-
Check that `last_task_index` is non-negative, and that it is not
|
92
|
-
smaller than `first_task_index`.
|
93
|
-
"""
|
94
|
-
if v is not None and v < 0:
|
95
|
-
raise ValueError(
|
96
|
-
f"last_task_index cannot be negative (given: {v})"
|
97
|
-
)
|
98
|
-
|
99
|
-
first_task_index = info.data.get("first_task_index")
|
100
|
-
last_task_index = v
|
56
|
+
def validate_first_last_indices(cls, values):
|
57
|
+
first_task_index = values.get("first_task_index")
|
58
|
+
last_task_index = values.get("last_task_index")
|
59
|
+
|
101
60
|
if first_task_index is not None and last_task_index is not None:
|
102
61
|
if first_task_index > last_task_index:
|
103
62
|
raise ValueError(
|
104
63
|
f"{first_task_index=} cannot be larger than "
|
105
64
|
f"{last_task_index=}"
|
106
65
|
)
|
107
|
-
return
|
66
|
+
return values
|
108
67
|
|
109
68
|
|
110
69
|
class JobReadV2(BaseModel):
|
@@ -126,7 +85,7 @@ class JobReadV2(BaseModel):
|
|
126
85
|
first_task_index: Optional[int] = None
|
127
86
|
last_task_index: Optional[int] = None
|
128
87
|
worker_init: Optional[str] = None
|
129
|
-
attribute_filters:
|
88
|
+
attribute_filters: AttributeFilters
|
130
89
|
type_filters: dict[str, bool]
|
131
90
|
|
132
91
|
@field_serializer("start_timestamp")
|
@@ -1,14 +1,13 @@
|
|
1
|
-
from typing import Any
|
2
1
|
from typing import Literal
|
3
2
|
from typing import Optional
|
4
3
|
|
5
4
|
from pydantic import BaseModel
|
6
5
|
from pydantic import Field
|
7
|
-
from pydantic import field_validator
|
8
|
-
from pydantic import HttpUrl
|
9
6
|
from pydantic import model_validator
|
10
7
|
|
11
|
-
from
|
8
|
+
from fractal_server.types import DictStrAny
|
9
|
+
from fractal_server.types import HttpUrlStr
|
10
|
+
from fractal_server.types import NonEmptyStr
|
12
11
|
|
13
12
|
|
14
13
|
class TaskManifestV2(BaseModel):
|
@@ -46,12 +45,12 @@ class TaskManifestV2(BaseModel):
|
|
46
45
|
executable_parallel: Optional[str] = None
|
47
46
|
input_types: dict[str, bool] = Field(default_factory=dict)
|
48
47
|
output_types: dict[str, bool] = Field(default_factory=dict)
|
49
|
-
meta_non_parallel:
|
50
|
-
meta_parallel:
|
51
|
-
args_schema_non_parallel: Optional[
|
52
|
-
args_schema_parallel: Optional[
|
48
|
+
meta_non_parallel: DictStrAny = Field(default_factory=dict)
|
49
|
+
meta_parallel: DictStrAny = Field(default_factory=dict)
|
50
|
+
args_schema_non_parallel: Optional[DictStrAny] = None
|
51
|
+
args_schema_parallel: Optional[DictStrAny] = None
|
53
52
|
docs_info: Optional[str] = None
|
54
|
-
docs_link: Optional[
|
53
|
+
docs_link: Optional[HttpUrlStr] = None
|
55
54
|
|
56
55
|
category: Optional[str] = None
|
57
56
|
modality: Optional[str] = None
|
@@ -113,13 +112,6 @@ class TaskManifestV2(BaseModel):
|
|
113
112
|
|
114
113
|
return self
|
115
114
|
|
116
|
-
@field_validator("docs_link", mode="after")
|
117
|
-
@classmethod
|
118
|
-
def validate_docs_link(cls, value):
|
119
|
-
if value is not None:
|
120
|
-
HttpUrl(value)
|
121
|
-
return value
|
122
|
-
|
123
115
|
|
124
116
|
class ManifestV2(BaseModel):
|
125
117
|
"""
|
@@ -145,11 +137,11 @@ class ManifestV2(BaseModel):
|
|
145
137
|
Label of how `args_schema`s were generated (e.g. `pydantic_v1`).
|
146
138
|
"""
|
147
139
|
|
148
|
-
manifest_version:
|
140
|
+
manifest_version: Literal["2"]
|
149
141
|
task_list: list[TaskManifestV2]
|
150
142
|
has_args_schemas: bool = False
|
151
143
|
args_schema_version: Optional[str] = None
|
152
|
-
authors: Optional[
|
144
|
+
authors: Optional[NonEmptyStr] = None
|
153
145
|
|
154
146
|
@model_validator(mode="after")
|
155
147
|
def _check_args_schemas_are_present(self):
|
@@ -185,10 +177,3 @@ class ManifestV2(BaseModel):
|
|
185
177
|
)
|
186
178
|
)
|
187
179
|
return self
|
188
|
-
|
189
|
-
@field_validator("manifest_version")
|
190
|
-
@classmethod
|
191
|
-
def manifest_version_2(cls, value):
|
192
|
-
if value != "2":
|
193
|
-
raise ValueError(f"Wrong manifest version (given {value})")
|
194
|
-
return value
|
@@ -1,21 +1,18 @@
|
|
1
1
|
from datetime import datetime
|
2
|
-
from typing import Optional
|
3
2
|
|
4
3
|
from pydantic import BaseModel
|
5
4
|
from pydantic import ConfigDict
|
6
5
|
from pydantic import field_serializer
|
7
|
-
from pydantic import field_validator
|
8
6
|
from pydantic.types import AwareDatetime
|
9
7
|
|
10
|
-
from
|
11
|
-
from .._validators import NonEmptyString
|
8
|
+
from fractal_server.types import NonEmptyStr
|
12
9
|
|
13
10
|
|
14
11
|
class ProjectCreateV2(BaseModel):
|
15
12
|
|
16
13
|
model_config = ConfigDict(extra="forbid")
|
17
14
|
|
18
|
-
name:
|
15
|
+
name: NonEmptyStr
|
19
16
|
|
20
17
|
|
21
18
|
class ProjectReadV2(BaseModel):
|
@@ -33,9 +30,4 @@ class ProjectUpdateV2(BaseModel):
|
|
33
30
|
|
34
31
|
model_config = ConfigDict(extra="forbid")
|
35
32
|
|
36
|
-
name:
|
37
|
-
|
38
|
-
@field_validator("name")
|
39
|
-
@classmethod
|
40
|
-
def _cant_set_none(cls, v):
|
41
|
-
return cant_set_none(v)
|
33
|
+
name: NonEmptyStr = None
|
@@ -5,16 +5,15 @@ from typing import Optional
|
|
5
5
|
from pydantic import BaseModel
|
6
6
|
from pydantic import ConfigDict
|
7
7
|
from pydantic import Field
|
8
|
-
from pydantic import field_validator
|
9
|
-
from pydantic import HttpUrl
|
10
8
|
from pydantic import model_validator
|
11
9
|
|
12
|
-
from .._validators import cant_set_none
|
13
|
-
from fractal_server.app.schemas._validators import NonEmptyString
|
14
|
-
from fractal_server.app.schemas._validators import val_unique_list
|
15
|
-
from fractal_server.app.schemas._validators import valdict_keys
|
16
10
|
from fractal_server.logger import set_logger
|
17
11
|
from fractal_server.string_tools import validate_cmd
|
12
|
+
from fractal_server.types import DictStrAny
|
13
|
+
from fractal_server.types import HttpUrlStr
|
14
|
+
from fractal_server.types import ListUniqueNonEmptyString
|
15
|
+
from fractal_server.types import NonEmptyStr
|
16
|
+
from fractal_server.types import TypeFilters
|
18
17
|
|
19
18
|
TaskTypeType = Literal[
|
20
19
|
"compound",
|
@@ -31,42 +30,30 @@ logger = set_logger(__name__)
|
|
31
30
|
class TaskCreateV2(BaseModel):
|
32
31
|
model_config = ConfigDict(extra="forbid")
|
33
32
|
|
34
|
-
name:
|
33
|
+
name: NonEmptyStr
|
35
34
|
|
36
|
-
command_non_parallel:
|
37
|
-
command_parallel:
|
35
|
+
command_non_parallel: NonEmptyStr = None
|
36
|
+
command_parallel: NonEmptyStr = None
|
38
37
|
|
39
|
-
meta_non_parallel: Optional[
|
40
|
-
meta_parallel: Optional[
|
41
|
-
version:
|
42
|
-
args_schema_non_parallel: Optional[
|
43
|
-
args_schema_parallel: Optional[
|
44
|
-
args_schema_version:
|
38
|
+
meta_non_parallel: Optional[DictStrAny] = None
|
39
|
+
meta_parallel: Optional[DictStrAny] = None
|
40
|
+
version: NonEmptyStr = None
|
41
|
+
args_schema_non_parallel: Optional[DictStrAny] = None
|
42
|
+
args_schema_parallel: Optional[DictStrAny] = None
|
43
|
+
args_schema_version: NonEmptyStr = None
|
45
44
|
docs_info: Optional[str] = None
|
46
|
-
docs_link: Optional[
|
45
|
+
docs_link: Optional[HttpUrlStr] = None
|
47
46
|
|
48
|
-
input_types:
|
49
|
-
output_types:
|
47
|
+
input_types: TypeFilters = Field(default={})
|
48
|
+
output_types: TypeFilters = Field(default={})
|
50
49
|
|
51
|
-
category: Optional[
|
52
|
-
modality: Optional[
|
53
|
-
tags:
|
54
|
-
authors: Optional[
|
50
|
+
category: Optional[NonEmptyStr] = None
|
51
|
+
modality: Optional[NonEmptyStr] = None
|
52
|
+
tags: ListUniqueNonEmptyString = Field(default_factory=list)
|
53
|
+
authors: Optional[NonEmptyStr] = None
|
55
54
|
|
56
55
|
type: Optional[TaskTypeType] = None
|
57
56
|
|
58
|
-
# Validators
|
59
|
-
|
60
|
-
@field_validator(
|
61
|
-
"command_non_parallel",
|
62
|
-
"command_parallel",
|
63
|
-
"version",
|
64
|
-
"args_schema_version",
|
65
|
-
)
|
66
|
-
@classmethod
|
67
|
-
def _cant_set_none(cls, v):
|
68
|
-
return cant_set_none(v)
|
69
|
-
|
70
57
|
@model_validator(mode="after")
|
71
58
|
def validate_commands(self):
|
72
59
|
command_parallel = self.command_parallel
|
@@ -100,37 +87,6 @@ class TaskCreateV2(BaseModel):
|
|
100
87
|
|
101
88
|
return self
|
102
89
|
|
103
|
-
_meta_non_parallel = field_validator("meta_non_parallel")(
|
104
|
-
classmethod(valdict_keys("meta_non_parallel"))
|
105
|
-
)
|
106
|
-
_meta_parallel = field_validator("meta_parallel")(
|
107
|
-
classmethod(valdict_keys("meta_parallel"))
|
108
|
-
)
|
109
|
-
_args_schema_non_parallel = field_validator("args_schema_non_parallel")(
|
110
|
-
classmethod(valdict_keys("args_schema_non_parallel"))
|
111
|
-
)
|
112
|
-
_args_schema_parallel = field_validator("args_schema_parallel")(
|
113
|
-
classmethod(valdict_keys("args_schema_parallel"))
|
114
|
-
)
|
115
|
-
_input_types = field_validator("input_types")(
|
116
|
-
classmethod(valdict_keys("input_types"))
|
117
|
-
)
|
118
|
-
_output_types = field_validator("output_types")(
|
119
|
-
classmethod(valdict_keys("output_types"))
|
120
|
-
)
|
121
|
-
|
122
|
-
@field_validator("tags")
|
123
|
-
@classmethod
|
124
|
-
def validate_list_of_strings(cls, value):
|
125
|
-
return val_unique_list("tags")(cls, value)
|
126
|
-
|
127
|
-
@field_validator("docs_link", mode="after")
|
128
|
-
@classmethod
|
129
|
-
def validate_docs_link(cls, value):
|
130
|
-
if value is not None:
|
131
|
-
HttpUrl(value)
|
132
|
-
return value
|
133
|
-
|
134
90
|
|
135
91
|
class TaskReadV2(BaseModel):
|
136
92
|
id: int
|
@@ -162,56 +118,30 @@ class TaskReadV2(BaseModel):
|
|
162
118
|
class TaskUpdateV2(BaseModel):
|
163
119
|
model_config = ConfigDict(extra="forbid")
|
164
120
|
|
165
|
-
command_parallel:
|
166
|
-
command_non_parallel:
|
167
|
-
input_types:
|
168
|
-
output_types:
|
169
|
-
|
170
|
-
category: Optional[NonEmptyString] = None
|
171
|
-
modality: Optional[NonEmptyString] = None
|
172
|
-
authors: Optional[NonEmptyString] = None
|
173
|
-
tags: Optional[list[NonEmptyString]] = None
|
174
|
-
|
175
|
-
# Validators
|
176
|
-
|
177
|
-
@field_validator("command_parallel", "command_non_parallel")
|
178
|
-
@classmethod
|
179
|
-
def _cant_set_none(cls, v):
|
180
|
-
return cant_set_none(v)
|
181
|
-
|
182
|
-
@field_validator("input_types", "output_types")
|
183
|
-
@classmethod
|
184
|
-
def val_is_dict(cls, v):
|
185
|
-
if not isinstance(v, dict):
|
186
|
-
raise ValueError
|
187
|
-
return v
|
188
|
-
|
189
|
-
_input_types = field_validator("input_types")(
|
190
|
-
classmethod(valdict_keys("input_types"))
|
191
|
-
)
|
192
|
-
_output_types = field_validator("output_types")(
|
193
|
-
classmethod(valdict_keys("output_types"))
|
194
|
-
)
|
121
|
+
command_parallel: NonEmptyStr = None
|
122
|
+
command_non_parallel: NonEmptyStr = None
|
123
|
+
input_types: TypeFilters = None
|
124
|
+
output_types: TypeFilters = None
|
195
125
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
126
|
+
category: Optional[NonEmptyStr] = None
|
127
|
+
modality: Optional[NonEmptyStr] = None
|
128
|
+
authors: Optional[NonEmptyStr] = None
|
129
|
+
tags: Optional[ListUniqueNonEmptyString] = None
|
200
130
|
|
201
131
|
|
202
132
|
class TaskImportV2(BaseModel):
|
203
133
|
model_config = ConfigDict(extra="forbid")
|
204
134
|
|
205
|
-
pkg_name:
|
206
|
-
version: Optional[
|
207
|
-
name:
|
135
|
+
pkg_name: NonEmptyStr
|
136
|
+
version: Optional[NonEmptyStr] = None
|
137
|
+
name: NonEmptyStr
|
208
138
|
|
209
139
|
|
210
140
|
class TaskImportV2Legacy(BaseModel):
|
211
|
-
source:
|
141
|
+
source: NonEmptyStr
|
212
142
|
|
213
143
|
|
214
144
|
class TaskExportV2(BaseModel):
|
215
|
-
pkg_name:
|
216
|
-
version: Optional[
|
217
|
-
name:
|
145
|
+
pkg_name: NonEmptyStr
|
146
|
+
version: Optional[NonEmptyStr] = None
|
147
|
+
name: NonEmptyStr
|
@@ -1,4 +1,3 @@
|
|
1
|
-
from pathlib import Path
|
2
1
|
from typing import Literal
|
3
2
|
from typing import Optional
|
4
3
|
|
@@ -7,9 +6,11 @@ from pydantic import ConfigDict
|
|
7
6
|
from pydantic import field_validator
|
8
7
|
from pydantic import model_validator
|
9
8
|
|
10
|
-
from .._validators import NonEmptyString
|
11
9
|
from fractal_server.app.schemas.v2 import ManifestV2
|
12
10
|
from fractal_server.string_tools import validate_cmd
|
11
|
+
from fractal_server.types import AbsolutePathStr
|
12
|
+
from fractal_server.types import DictStrStr
|
13
|
+
from fractal_server.types import NonEmptyStr
|
13
14
|
|
14
15
|
|
15
16
|
class WheelFile(BaseModel):
|
@@ -46,57 +47,28 @@ class TaskCollectPipV2(BaseModel):
|
|
46
47
|
"""
|
47
48
|
|
48
49
|
model_config = ConfigDict(extra="forbid")
|
49
|
-
package: Optional[
|
50
|
-
package_version: Optional[
|
51
|
-
package_extras: Optional[
|
50
|
+
package: Optional[NonEmptyStr] = None
|
51
|
+
package_version: Optional[NonEmptyStr] = None
|
52
|
+
package_extras: Optional[NonEmptyStr] = None
|
52
53
|
python_version: Optional[Literal["3.9", "3.10", "3.11", "3.12"]] = None
|
53
|
-
pinned_package_versions: Optional[
|
54
|
+
pinned_package_versions: Optional[DictStrStr] = None
|
54
55
|
|
55
|
-
@field_validator(
|
56
|
+
@field_validator(
|
57
|
+
"package", "package_version", "package_extras", mode="after"
|
58
|
+
)
|
56
59
|
@classmethod
|
57
|
-
def
|
58
|
-
if value is None:
|
59
|
-
|
60
|
-
validate_cmd(value, attribute_name="package")
|
61
|
-
return value
|
62
|
-
|
63
|
-
@field_validator("package_version")
|
64
|
-
@classmethod
|
65
|
-
def package_version_validator(cls, value: Optional[str]) -> Optional[str]:
|
66
|
-
if value is None:
|
67
|
-
return value
|
68
|
-
validate_cmd(value, attribute_name="package_version")
|
69
|
-
return value
|
70
|
-
|
71
|
-
@field_validator("pinned_package_versions")
|
72
|
-
@classmethod
|
73
|
-
def pinned_package_versions_validator(cls, value):
|
74
|
-
if value is None:
|
75
|
-
return value
|
76
|
-
|
77
|
-
old_keys = list(value.keys())
|
78
|
-
new_keys = [key.strip() for key in old_keys]
|
79
|
-
if any(k == "" for k in new_keys):
|
80
|
-
raise ValueError(f"Empty string in {new_keys}.")
|
81
|
-
if len(new_keys) != len(set(new_keys)):
|
82
|
-
raise ValueError(
|
83
|
-
f"Dictionary contains multiple identical keys: {value}."
|
84
|
-
)
|
85
|
-
for old_key, new_key in zip(old_keys, new_keys):
|
86
|
-
if new_key != old_key:
|
87
|
-
value[new_key] = value.pop(old_key)
|
88
|
-
|
89
|
-
for pkg, version in value.items():
|
90
|
-
validate_cmd(pkg)
|
91
|
-
validate_cmd(version)
|
60
|
+
def validate_commands(cls, value):
|
61
|
+
if value is not None:
|
62
|
+
validate_cmd(value)
|
92
63
|
return value
|
93
64
|
|
94
|
-
@field_validator("
|
65
|
+
@field_validator("pinned_package_versions", mode="after")
|
95
66
|
@classmethod
|
96
|
-
def
|
97
|
-
if value is None:
|
98
|
-
|
99
|
-
|
67
|
+
def validate_pinned_package_versions(cls, value):
|
68
|
+
if value is not None:
|
69
|
+
for pkg, version in value.items():
|
70
|
+
validate_cmd(pkg)
|
71
|
+
validate_cmd(version)
|
100
72
|
return value
|
101
73
|
|
102
74
|
|
@@ -120,11 +92,18 @@ class TaskCollectCustomV2(BaseModel):
|
|
120
92
|
|
121
93
|
model_config = ConfigDict(extra="forbid")
|
122
94
|
manifest: ManifestV2
|
123
|
-
python_interpreter:
|
124
|
-
label:
|
125
|
-
package_root: Optional[
|
126
|
-
package_name: Optional[
|
127
|
-
version: Optional[
|
95
|
+
python_interpreter: AbsolutePathStr
|
96
|
+
label: NonEmptyStr
|
97
|
+
package_root: Optional[AbsolutePathStr] = None
|
98
|
+
package_name: Optional[NonEmptyStr] = None
|
99
|
+
version: Optional[NonEmptyStr] = None
|
100
|
+
|
101
|
+
@field_validator("package_name", mode="after")
|
102
|
+
@classmethod
|
103
|
+
def validate_package_name(cls, value):
|
104
|
+
if value is not None:
|
105
|
+
validate_cmd(value)
|
106
|
+
return value
|
128
107
|
|
129
108
|
@model_validator(mode="before")
|
130
109
|
@classmethod
|
@@ -139,32 +118,3 @@ class TaskCollectCustomV2(BaseModel):
|
|
139
118
|
"'package_root' and 'package_name'"
|
140
119
|
)
|
141
120
|
return values
|
142
|
-
|
143
|
-
@field_validator("package_name")
|
144
|
-
@classmethod
|
145
|
-
def package_name_validator(cls, value: str):
|
146
|
-
"""
|
147
|
-
Remove all whitespace characters, then check for invalid code.
|
148
|
-
"""
|
149
|
-
if value is not None:
|
150
|
-
validate_cmd(value)
|
151
|
-
value = value.replace(" ", "")
|
152
|
-
return value
|
153
|
-
|
154
|
-
@field_validator("package_root")
|
155
|
-
@classmethod
|
156
|
-
def package_root_validator(cls, value):
|
157
|
-
if (value is not None) and (not Path(value).is_absolute()):
|
158
|
-
raise ValueError(
|
159
|
-
f"'package_root' must be an absolute path: (given {value})."
|
160
|
-
)
|
161
|
-
return value
|
162
|
-
|
163
|
-
@field_validator("python_interpreter")
|
164
|
-
@classmethod
|
165
|
-
def python_interpreter_validator(cls, value):
|
166
|
-
if not Path(value).is_absolute():
|
167
|
-
raise ValueError(
|
168
|
-
f"Python interpreter path must be absolute: (given {value})."
|
169
|
-
)
|
170
|
-
return value
|