fractal-server 2.14.3a0__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.3a0.dist-info → fractal_server-2.14.4.dist-info}/METADATA +1 -1
- {fractal_server-2.14.3a0.dist-info → fractal_server-2.14.4.dist-info}/RECORD +38 -36
- fractal_server/app/runner/compress_folder.py +0 -144
- 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.3a0.dist-info → fractal_server-2.14.4.dist-info}/LICENSE +0 -0
- {fractal_server-2.14.3a0.dist-info → fractal_server-2.14.4.dist-info}/WHEEL +0 -0
- {fractal_server-2.14.3a0.dist-info → fractal_server-2.14.4.dist-info}/entry_points.txt +0 -0
@@ -6,14 +6,12 @@ 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.types import AwareDatetime
|
11
10
|
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from .task import TaskReadV2
|
11
|
+
from fractal_server.app.schemas.v2.task import TaskReadV2
|
12
|
+
from fractal_server.types import AbsolutePathStr
|
13
|
+
from fractal_server.types import DictStrStr
|
14
|
+
from fractal_server.types import NonEmptyStr
|
17
15
|
|
18
16
|
|
19
17
|
class TaskGroupV2OriginEnum(str, Enum):
|
@@ -43,31 +41,13 @@ class TaskGroupCreateV2(BaseModel):
|
|
43
41
|
origin: TaskGroupV2OriginEnum
|
44
42
|
pkg_name: str
|
45
43
|
version: Optional[str] = None
|
46
|
-
python_version:
|
47
|
-
path:
|
48
|
-
venv_path:
|
49
|
-
wheel_path:
|
50
|
-
pip_extras:
|
44
|
+
python_version: NonEmptyStr = None
|
45
|
+
path: AbsolutePathStr = None
|
46
|
+
venv_path: AbsolutePathStr = None
|
47
|
+
wheel_path: AbsolutePathStr = None
|
48
|
+
pip_extras: NonEmptyStr = None
|
51
49
|
pip_freeze: Optional[str] = None
|
52
|
-
pinned_package_versions:
|
53
|
-
|
54
|
-
# Validators
|
55
|
-
|
56
|
-
@field_validator("python_version", "pip_extras")
|
57
|
-
@classmethod
|
58
|
-
def _cant_set_none(cls, v):
|
59
|
-
return cant_set_none(v)
|
60
|
-
|
61
|
-
_path = field_validator("path")(classmethod(val_absolute_path("path")))
|
62
|
-
_venv_path = field_validator("venv_path")(
|
63
|
-
classmethod(val_absolute_path("venv_path"))
|
64
|
-
)
|
65
|
-
_wheel_path = field_validator("wheel_path")(
|
66
|
-
classmethod(val_absolute_path("wheel_path"))
|
67
|
-
)
|
68
|
-
_pinned_package_versions = field_validator("pinned_package_versions")(
|
69
|
-
valdict_keys("pinned_package_versions")
|
70
|
-
)
|
50
|
+
pinned_package_versions: DictStrStr = Field(default_factory=dict)
|
71
51
|
|
72
52
|
|
73
53
|
class TaskGroupCreateV2Strict(TaskGroupCreateV2):
|
@@ -75,10 +55,10 @@ class TaskGroupCreateV2Strict(TaskGroupCreateV2):
|
|
75
55
|
A strict version of TaskGroupCreateV2, to be used for task collection.
|
76
56
|
"""
|
77
57
|
|
78
|
-
path:
|
79
|
-
venv_path:
|
80
|
-
version:
|
81
|
-
python_version:
|
58
|
+
path: AbsolutePathStr
|
59
|
+
venv_path: AbsolutePathStr
|
60
|
+
version: NonEmptyStr
|
61
|
+
python_version: NonEmptyStr
|
82
62
|
|
83
63
|
|
84
64
|
class TaskGroupReadV2(BaseModel):
|
@@ -4,23 +4,24 @@ from typing import Optional
|
|
4
4
|
from pydantic import BaseModel
|
5
5
|
from pydantic import ConfigDict
|
6
6
|
from pydantic import field_serializer
|
7
|
-
from pydantic import field_validator
|
8
7
|
from pydantic.types import AwareDatetime
|
9
8
|
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from .
|
13
|
-
from .workflowtask import
|
14
|
-
from .workflowtask import
|
15
|
-
|
16
|
-
|
9
|
+
from fractal_server.app.schemas.v2.project import ProjectReadV2
|
10
|
+
from fractal_server.app.schemas.v2.workflowtask import WorkflowTaskExportV2
|
11
|
+
from fractal_server.app.schemas.v2.workflowtask import WorkflowTaskImportV2
|
12
|
+
from fractal_server.app.schemas.v2.workflowtask import WorkflowTaskReadV2
|
13
|
+
from fractal_server.app.schemas.v2.workflowtask import (
|
14
|
+
WorkflowTaskReadV2WithWarning,
|
15
|
+
)
|
16
|
+
from fractal_server.types import ListUniqueNonNegativeInt
|
17
|
+
from fractal_server.types import NonEmptyStr
|
17
18
|
|
18
19
|
|
19
20
|
class WorkflowCreateV2(BaseModel):
|
20
21
|
|
21
22
|
model_config = ConfigDict(extra="forbid")
|
22
23
|
|
23
|
-
name:
|
24
|
+
name: NonEmptyStr
|
24
25
|
|
25
26
|
|
26
27
|
class WorkflowReadV2(BaseModel):
|
@@ -45,24 +46,8 @@ class WorkflowUpdateV2(BaseModel):
|
|
45
46
|
|
46
47
|
model_config = ConfigDict(extra="forbid")
|
47
48
|
|
48
|
-
name:
|
49
|
-
reordered_workflowtask_ids: Optional[
|
50
|
-
|
51
|
-
# Validators
|
52
|
-
|
53
|
-
@field_validator("name")
|
54
|
-
@classmethod
|
55
|
-
def _cant_set_none(cls, v):
|
56
|
-
return cant_set_none(v)
|
57
|
-
|
58
|
-
@field_validator("reordered_workflowtask_ids")
|
59
|
-
@classmethod
|
60
|
-
def check_positive_and_unique(cls, value):
|
61
|
-
if any(i < 0 for i in value):
|
62
|
-
raise ValueError("Negative `id` in `reordered_workflowtask_ids`")
|
63
|
-
if len(value) != len(set(value)):
|
64
|
-
raise ValueError("`reordered_workflowtask_ids` has repetitions")
|
65
|
-
return value
|
49
|
+
name: NonEmptyStr = None
|
50
|
+
reordered_workflowtask_ids: Optional[ListUniqueNonNegativeInt] = None
|
66
51
|
|
67
52
|
|
68
53
|
class WorkflowImportV2(BaseModel):
|
@@ -74,7 +59,7 @@ class WorkflowImportV2(BaseModel):
|
|
74
59
|
"""
|
75
60
|
|
76
61
|
model_config = ConfigDict(extra="forbid")
|
77
|
-
name:
|
62
|
+
name: NonEmptyStr
|
78
63
|
task_list: list[WorkflowTaskImportV2]
|
79
64
|
|
80
65
|
|
@@ -5,73 +5,26 @@ from typing import Union
|
|
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
8
|
from pydantic import model_validator
|
10
9
|
|
11
|
-
from .._filter_validators import validate_type_filters
|
12
|
-
from .._validators import root_validate_dict_keys
|
13
|
-
from .._validators import valdict_keys
|
14
10
|
from .task import TaskExportV2
|
15
11
|
from .task import TaskImportV2
|
16
12
|
from .task import TaskImportV2Legacy
|
17
13
|
from .task import TaskReadV2
|
18
14
|
from .task import TaskTypeType
|
19
|
-
|
20
|
-
|
15
|
+
from fractal_server.types import DictStrAny
|
16
|
+
from fractal_server.types import TypeFilters
|
17
|
+
from fractal_server.types import WorkflowTaskArgument
|
21
18
|
|
22
19
|
|
23
20
|
class WorkflowTaskCreateV2(BaseModel):
|
24
21
|
model_config = ConfigDict(extra="forbid")
|
25
22
|
|
26
|
-
meta_non_parallel: Optional[
|
27
|
-
meta_parallel: Optional[
|
28
|
-
args_non_parallel: Optional[
|
29
|
-
args_parallel: Optional[
|
30
|
-
type_filters:
|
31
|
-
|
32
|
-
# Validators
|
33
|
-
_dict_keys = model_validator(mode="before")(
|
34
|
-
classmethod(root_validate_dict_keys)
|
35
|
-
)
|
36
|
-
_type_filters = field_validator("type_filters")(
|
37
|
-
classmethod(validate_type_filters)
|
38
|
-
)
|
39
|
-
_meta_non_parallel = field_validator("meta_non_parallel")(
|
40
|
-
classmethod(valdict_keys("meta_non_parallel"))
|
41
|
-
)
|
42
|
-
_meta_parallel = field_validator("meta_parallel")(
|
43
|
-
classmethod(valdict_keys("meta_parallel"))
|
44
|
-
)
|
45
|
-
|
46
|
-
@field_validator("args_non_parallel")
|
47
|
-
@classmethod
|
48
|
-
def validate_args_non_parallel(cls, value):
|
49
|
-
if value is None:
|
50
|
-
return
|
51
|
-
valdict_keys("args_non_parallel")(cls, value)
|
52
|
-
args_keys = set(value.keys())
|
53
|
-
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
54
|
-
if intersect_keys:
|
55
|
-
raise ValueError(
|
56
|
-
"`args` contains the following forbidden keys: "
|
57
|
-
f"{intersect_keys}"
|
58
|
-
)
|
59
|
-
return value
|
60
|
-
|
61
|
-
@field_validator("args_parallel")
|
62
|
-
@classmethod
|
63
|
-
def validate_args_parallel(cls, value):
|
64
|
-
if value is None:
|
65
|
-
return
|
66
|
-
valdict_keys("args_parallel")(cls, value)
|
67
|
-
args_keys = set(value.keys())
|
68
|
-
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
69
|
-
if intersect_keys:
|
70
|
-
raise ValueError(
|
71
|
-
"`args` contains the following forbidden keys: "
|
72
|
-
f"{intersect_keys}"
|
73
|
-
)
|
74
|
-
return value
|
23
|
+
meta_non_parallel: Optional[DictStrAny] = None
|
24
|
+
meta_parallel: Optional[DictStrAny] = None
|
25
|
+
args_non_parallel: Optional[WorkflowTaskArgument] = None
|
26
|
+
args_parallel: Optional[WorkflowTaskArgument] = None
|
27
|
+
type_filters: TypeFilters = Field(default_factory=dict)
|
75
28
|
|
76
29
|
|
77
30
|
class WorkflowTaskReplaceV2(BaseModel):
|
@@ -106,70 +59,25 @@ class WorkflowTaskReadV2WithWarning(WorkflowTaskReadV2):
|
|
106
59
|
class WorkflowTaskUpdateV2(BaseModel):
|
107
60
|
model_config = ConfigDict(extra="forbid")
|
108
61
|
|
109
|
-
meta_non_parallel: Optional[
|
110
|
-
meta_parallel: Optional[
|
111
|
-
args_non_parallel: Optional[
|
112
|
-
args_parallel: Optional[
|
113
|
-
type_filters:
|
114
|
-
|
115
|
-
# Validators
|
116
|
-
_dict_keys = model_validator(mode="before")(
|
117
|
-
classmethod(root_validate_dict_keys)
|
118
|
-
)
|
119
|
-
_type_filters = field_validator("type_filters")(
|
120
|
-
classmethod(validate_type_filters)
|
121
|
-
)
|
122
|
-
_meta_non_parallel = field_validator("meta_non_parallel")(
|
123
|
-
classmethod(valdict_keys("meta_non_parallel"))
|
124
|
-
)
|
125
|
-
_meta_parallel = field_validator("meta_parallel")(
|
126
|
-
classmethod(valdict_keys("meta_parallel"))
|
127
|
-
)
|
128
|
-
|
129
|
-
@field_validator("args_non_parallel")
|
130
|
-
@classmethod
|
131
|
-
def validate_args_non_parallel(cls, value):
|
132
|
-
if value is None:
|
133
|
-
return
|
134
|
-
valdict_keys("args_non_parallel")(cls, value)
|
135
|
-
args_keys = set(value.keys())
|
136
|
-
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
137
|
-
if intersect_keys:
|
138
|
-
raise ValueError(
|
139
|
-
"`args` contains the following forbidden keys: "
|
140
|
-
f"{intersect_keys}"
|
141
|
-
)
|
142
|
-
return value
|
143
|
-
|
144
|
-
@field_validator("args_parallel")
|
145
|
-
@classmethod
|
146
|
-
def validate_args_parallel(cls, value):
|
147
|
-
if value is None:
|
148
|
-
return
|
149
|
-
valdict_keys("args_parallel")(cls, value)
|
150
|
-
args_keys = set(value.keys())
|
151
|
-
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
152
|
-
if intersect_keys:
|
153
|
-
raise ValueError(
|
154
|
-
"`args` contains the following forbidden keys: "
|
155
|
-
f"{intersect_keys}"
|
156
|
-
)
|
157
|
-
return value
|
62
|
+
meta_non_parallel: Optional[DictStrAny] = None
|
63
|
+
meta_parallel: Optional[DictStrAny] = None
|
64
|
+
args_non_parallel: Optional[WorkflowTaskArgument] = None
|
65
|
+
args_parallel: Optional[WorkflowTaskArgument] = None
|
66
|
+
type_filters: TypeFilters = None
|
158
67
|
|
159
68
|
|
160
69
|
class WorkflowTaskImportV2(BaseModel):
|
161
70
|
model_config = ConfigDict(extra="forbid")
|
162
71
|
|
163
|
-
meta_non_parallel: Optional[
|
164
|
-
meta_parallel: Optional[
|
165
|
-
args_non_parallel: Optional[
|
166
|
-
args_parallel: Optional[
|
167
|
-
type_filters: Optional[
|
72
|
+
meta_non_parallel: Optional[DictStrAny] = None
|
73
|
+
meta_parallel: Optional[DictStrAny] = None
|
74
|
+
args_non_parallel: Optional[DictStrAny] = None
|
75
|
+
args_parallel: Optional[DictStrAny] = None
|
76
|
+
type_filters: Optional[TypeFilters] = None
|
168
77
|
input_filters: Optional[dict[str, Any]] = None
|
169
78
|
|
170
79
|
task: Union[TaskImportV2, TaskImportV2Legacy]
|
171
80
|
|
172
|
-
# Validators
|
173
81
|
@model_validator(mode="before")
|
174
82
|
@classmethod
|
175
83
|
def update_legacy_filters(cls, values: dict):
|
@@ -198,22 +106,6 @@ class WorkflowTaskImportV2(BaseModel):
|
|
198
106
|
|
199
107
|
return values
|
200
108
|
|
201
|
-
_type_filters = field_validator("type_filters")(
|
202
|
-
classmethod(validate_type_filters)
|
203
|
-
)
|
204
|
-
_meta_non_parallel = field_validator("meta_non_parallel")(
|
205
|
-
classmethod(valdict_keys("meta_non_parallel"))
|
206
|
-
)
|
207
|
-
_meta_parallel = field_validator("meta_parallel")(
|
208
|
-
classmethod(valdict_keys("meta_parallel"))
|
209
|
-
)
|
210
|
-
_args_non_parallel = field_validator("args_non_parallel")(
|
211
|
-
classmethod(valdict_keys("args_non_parallel"))
|
212
|
-
)
|
213
|
-
_args_parallel = field_validator("args_parallel")(
|
214
|
-
classmethod(valdict_keys("args_parallel"))
|
215
|
-
)
|
216
|
-
|
217
109
|
|
218
110
|
class WorkflowTaskExportV2(BaseModel):
|
219
111
|
meta_non_parallel: Optional[dict[str, Any]] = None
|
fractal_server/config.py
CHANGED
@@ -34,6 +34,7 @@ from pydantic_settings import SettingsConfigDict
|
|
34
34
|
from sqlalchemy.engine import URL
|
35
35
|
|
36
36
|
import fractal_server
|
37
|
+
from fractal_server.types import AbsolutePathStr
|
37
38
|
|
38
39
|
|
39
40
|
class MailSettings(BaseModel):
|
@@ -280,45 +281,27 @@ class Settings(BaseSettings):
|
|
280
281
|
or a path relative to current working directory).
|
281
282
|
"""
|
282
283
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
on the current working directory).
|
289
|
-
"""
|
290
|
-
if v is None:
|
291
|
-
return None
|
292
|
-
FRACTAL_TASKS_DIR_path = Path(v)
|
293
|
-
if not FRACTAL_TASKS_DIR_path.is_absolute():
|
294
|
-
FRACTAL_TASKS_DIR_path = FRACTAL_TASKS_DIR_path.resolve()
|
295
|
-
logging.warning(
|
296
|
-
f'FRACTAL_TASKS_DIR="{v}" is not an absolute path; '
|
297
|
-
f'converting it to "{str(FRACTAL_TASKS_DIR_path)}"'
|
298
|
-
)
|
299
|
-
return FRACTAL_TASKS_DIR_path
|
284
|
+
FRACTAL_RUNNER_WORKING_BASE_DIR: Optional[Path] = None
|
285
|
+
"""
|
286
|
+
Base directory for job files (either an absolute path or a path relative to
|
287
|
+
current working directory).
|
288
|
+
"""
|
300
289
|
|
301
|
-
@field_validator(
|
290
|
+
@field_validator(
|
291
|
+
"FRACTAL_TASKS_DIR",
|
292
|
+
"FRACTAL_RUNNER_WORKING_BASE_DIR",
|
293
|
+
mode="after",
|
294
|
+
)
|
302
295
|
@classmethod
|
303
|
-
def
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
make it absolute (based on the current working directory).
|
308
|
-
"""
|
309
|
-
if v is None:
|
310
|
-
return None
|
311
|
-
FRACTAL_RUNNER_WORKING_BASE_DIR_path = Path(v)
|
312
|
-
if not FRACTAL_RUNNER_WORKING_BASE_DIR_path.is_absolute():
|
313
|
-
FRACTAL_RUNNER_WORKING_BASE_DIR_path = (
|
314
|
-
FRACTAL_RUNNER_WORKING_BASE_DIR_path.resolve()
|
315
|
-
)
|
296
|
+
def make_paths_absolute(cls, path: Optional[Path]) -> Optional[Path]:
|
297
|
+
if path is None or path.is_absolute():
|
298
|
+
return path
|
299
|
+
else:
|
316
300
|
logging.warning(
|
317
|
-
f'
|
318
|
-
"
|
319
|
-
f'"{str(FRACTAL_RUNNER_WORKING_BASE_DIR_path)}"'
|
301
|
+
f"'{path}' is not an absolute path; "
|
302
|
+
f"converting it to '{path.resolve()}'"
|
320
303
|
)
|
321
|
-
|
304
|
+
return path.resolve()
|
322
305
|
|
323
306
|
FRACTAL_RUNNER_BACKEND: Literal[
|
324
307
|
"local",
|
@@ -329,12 +312,6 @@ class Settings(BaseSettings):
|
|
329
312
|
Select which runner backend to use.
|
330
313
|
"""
|
331
314
|
|
332
|
-
FRACTAL_RUNNER_WORKING_BASE_DIR: Optional[Path] = None
|
333
|
-
"""
|
334
|
-
Base directory for running jobs / workflows. All artifacts required to set
|
335
|
-
up, run and tear down jobs are placed in subdirs of this directory.
|
336
|
-
"""
|
337
|
-
|
338
315
|
FRACTAL_LOGGING_LEVEL: int = logging.INFO
|
339
316
|
"""
|
340
317
|
Logging-level threshold for logging
|
@@ -363,27 +340,12 @@ class Settings(BaseSettings):
|
|
363
340
|
Path of JSON file with configuration for the SLURM backend.
|
364
341
|
"""
|
365
342
|
|
366
|
-
FRACTAL_SLURM_WORKER_PYTHON: Optional[
|
343
|
+
FRACTAL_SLURM_WORKER_PYTHON: Optional[AbsolutePathStr] = None
|
367
344
|
"""
|
368
345
|
Absolute path to Python interpreter that will run the jobs on the SLURM
|
369
346
|
nodes. If not specified, the same interpreter that runs the server is used.
|
370
347
|
"""
|
371
348
|
|
372
|
-
@field_validator("FRACTAL_SLURM_WORKER_PYTHON")
|
373
|
-
@classmethod
|
374
|
-
def absolute_FRACTAL_SLURM_WORKER_PYTHON(cls, v):
|
375
|
-
"""
|
376
|
-
If `FRACTAL_SLURM_WORKER_PYTHON` is a relative path, fail.
|
377
|
-
"""
|
378
|
-
if v is None:
|
379
|
-
return None
|
380
|
-
elif not Path(v).is_absolute():
|
381
|
-
raise FractalConfigurationError(
|
382
|
-
f"Non-absolute value for FRACTAL_SLURM_WORKER_PYTHON={v}"
|
383
|
-
)
|
384
|
-
else:
|
385
|
-
return v
|
386
|
-
|
387
349
|
FRACTAL_TASKS_PYTHON_DEFAULT_VERSION: Optional[
|
388
350
|
Literal["3.9", "3.10", "3.11", "3.12"]
|
389
351
|
] = None
|
@@ -507,27 +469,12 @@ class Settings(BaseSettings):
|
|
507
469
|
`JobExecutionError`.
|
508
470
|
"""
|
509
471
|
|
510
|
-
FRACTAL_PIP_CACHE_DIR: Optional[
|
472
|
+
FRACTAL_PIP_CACHE_DIR: Optional[AbsolutePathStr] = None
|
511
473
|
"""
|
512
474
|
Absolute path to the cache directory for `pip`; if unset,
|
513
475
|
`--no-cache-dir` is used.
|
514
476
|
"""
|
515
477
|
|
516
|
-
@field_validator("FRACTAL_PIP_CACHE_DIR")
|
517
|
-
@classmethod
|
518
|
-
def absolute_FRACTAL_PIP_CACHE_DIR(cls, v):
|
519
|
-
"""
|
520
|
-
If `FRACTAL_PIP_CACHE_DIR` is a relative path, fail.
|
521
|
-
"""
|
522
|
-
if v is None:
|
523
|
-
return None
|
524
|
-
elif not Path(v).is_absolute():
|
525
|
-
raise FractalConfigurationError(
|
526
|
-
f"Non-absolute value for FRACTAL_PIP_CACHE_DIR={v}"
|
527
|
-
)
|
528
|
-
else:
|
529
|
-
return v
|
530
|
-
|
531
478
|
@property
|
532
479
|
def PIP_CACHE_DIR_ARG(self) -> str:
|
533
480
|
"""
|
fractal_server/images/models.py
CHANGED
@@ -1,15 +1,14 @@
|
|
1
|
-
from typing import Any
|
2
1
|
from typing import Optional
|
3
|
-
from typing import Union
|
4
2
|
|
5
3
|
from pydantic import BaseModel
|
6
4
|
from pydantic import Field
|
7
|
-
from pydantic import field_validator
|
8
5
|
|
9
|
-
from fractal_server.
|
10
|
-
from fractal_server.
|
11
|
-
|
12
|
-
|
6
|
+
from fractal_server.types import DictStrAny
|
7
|
+
from fractal_server.types import ImageAttributes
|
8
|
+
from fractal_server.types import ImageAttributesWithNone
|
9
|
+
from fractal_server.types import ImageTypes
|
10
|
+
from fractal_server.types import ZarrDirStr
|
11
|
+
from fractal_server.types import ZarrUrlStr
|
13
12
|
|
14
13
|
|
15
14
|
class _SingleImageBase(BaseModel):
|
@@ -23,28 +22,11 @@ class _SingleImageBase(BaseModel):
|
|
23
22
|
types:
|
24
23
|
"""
|
25
24
|
|
26
|
-
zarr_url:
|
27
|
-
origin: Optional[
|
28
|
-
|
29
|
-
attributes: dict[str, Any] = Field(default_factory=dict)
|
30
|
-
types: dict[str, bool] = Field(default_factory=dict)
|
31
|
-
|
32
|
-
# Validators
|
33
|
-
_attributes = field_validator("attributes")(
|
34
|
-
classmethod(valdict_keys("attributes"))
|
35
|
-
)
|
36
|
-
_types = field_validator("types")(classmethod(valdict_keys("types")))
|
37
|
-
|
38
|
-
@field_validator("zarr_url")
|
39
|
-
@classmethod
|
40
|
-
def normalize_zarr_url(cls, v: str) -> str:
|
41
|
-
return normalize_url(v)
|
25
|
+
zarr_url: ZarrUrlStr
|
26
|
+
origin: Optional[ZarrDirStr] = None
|
42
27
|
|
43
|
-
|
44
|
-
|
45
|
-
def normalize_orig(cls, v: Optional[str]) -> Optional[str]:
|
46
|
-
if v is not None:
|
47
|
-
return normalize_url(v)
|
28
|
+
attributes: DictStrAny = Field(default_factory=dict)
|
29
|
+
types: ImageTypes = Field(default_factory=dict)
|
48
30
|
|
49
31
|
|
50
32
|
class SingleImageTaskOutput(_SingleImageBase):
|
@@ -52,19 +34,7 @@ class SingleImageTaskOutput(_SingleImageBase):
|
|
52
34
|
`SingleImageBase`, with scalar `attributes` values (`None` included).
|
53
35
|
"""
|
54
36
|
|
55
|
-
|
56
|
-
@classmethod
|
57
|
-
def validate_attributes(
|
58
|
-
cls, v: dict[str, Any]
|
59
|
-
) -> dict[str, Union[int, float, str, bool, None]]:
|
60
|
-
for key, value in v.items():
|
61
|
-
if not isinstance(value, (int, float, str, bool, type(None))):
|
62
|
-
raise ValueError(
|
63
|
-
f"SingleImageTaskOutput.attributes[{key}] must be a "
|
64
|
-
"scalar (int, float, str or bool). "
|
65
|
-
f"Given {value} ({type(value)})"
|
66
|
-
)
|
67
|
-
return v
|
37
|
+
attributes: ImageAttributesWithNone = Field(default_factory=dict)
|
68
38
|
|
69
39
|
|
70
40
|
class SingleImage(_SingleImageBase):
|
@@ -72,46 +42,10 @@ class SingleImage(_SingleImageBase):
|
|
72
42
|
`SingleImageBase`, with scalar `attributes` values (`None` excluded).
|
73
43
|
"""
|
74
44
|
|
75
|
-
|
76
|
-
@classmethod
|
77
|
-
def validate_attributes(
|
78
|
-
cls, v: dict[str, Any]
|
79
|
-
) -> dict[str, Union[int, float, str, bool]]:
|
80
|
-
for key, value in v.items():
|
81
|
-
if not isinstance(value, (int, float, str, bool)):
|
82
|
-
raise ValueError(
|
83
|
-
f"SingleImage.attributes[{key}] must be a scalar "
|
84
|
-
f"(int, float, str or bool). Given {value} ({type(value)})"
|
85
|
-
)
|
86
|
-
return v
|
45
|
+
attributes: ImageAttributes = Field(default_factory=dict)
|
87
46
|
|
88
47
|
|
89
48
|
class SingleImageUpdate(BaseModel):
|
90
|
-
zarr_url:
|
91
|
-
attributes:
|
92
|
-
types: Optional[
|
93
|
-
|
94
|
-
@field_validator("zarr_url")
|
95
|
-
@classmethod
|
96
|
-
def normalize_zarr_url(cls, v: str) -> str:
|
97
|
-
return normalize_url(v)
|
98
|
-
|
99
|
-
@field_validator("attributes")
|
100
|
-
@classmethod
|
101
|
-
def validate_attributes(
|
102
|
-
cls, v: dict[str, Any]
|
103
|
-
) -> dict[str, Union[int, float, str, bool]]:
|
104
|
-
if v is not None:
|
105
|
-
# validate keys
|
106
|
-
valdict_keys("attributes")(cls, v)
|
107
|
-
# validate values
|
108
|
-
for key, value in v.items():
|
109
|
-
if not isinstance(value, (int, float, str, bool)):
|
110
|
-
raise ValueError(
|
111
|
-
f"SingleImageUpdate.attributes[{key}] must be a scalar"
|
112
|
-
" (int, float, str or bool). "
|
113
|
-
f"Given {value} ({type(value)})"
|
114
|
-
)
|
115
|
-
return v
|
116
|
-
|
117
|
-
_types = field_validator("types")(classmethod(valdict_keys("types")))
|
49
|
+
zarr_url: ZarrUrlStr
|
50
|
+
attributes: ImageAttributes = None
|
51
|
+
types: Optional[ImageTypes] = None
|
fractal_server/images/tools.py
CHANGED
@@ -4,7 +4,7 @@ from typing import Literal
|
|
4
4
|
from typing import Optional
|
5
5
|
from typing import Union
|
6
6
|
|
7
|
-
from fractal_server.
|
7
|
+
from fractal_server.types import AttributeFilters
|
8
8
|
|
9
9
|
ImageSearch = dict[Literal["image", "index"], Union[int, dict[str, Any]]]
|
10
10
|
|
@@ -36,7 +36,7 @@ def match_filter(
|
|
36
36
|
*,
|
37
37
|
image: dict[str, Any],
|
38
38
|
type_filters: dict[str, bool],
|
39
|
-
attribute_filters:
|
39
|
+
attribute_filters: AttributeFilters,
|
40
40
|
) -> bool:
|
41
41
|
"""
|
42
42
|
Find whether an image matches a filter set.
|
@@ -66,7 +66,7 @@ def match_filter(
|
|
66
66
|
def filter_image_list(
|
67
67
|
images: list[dict[str, Any]],
|
68
68
|
type_filters: Optional[dict[str, bool]] = None,
|
69
|
-
attribute_filters: Optional[
|
69
|
+
attribute_filters: Optional[AttributeFilters] = None,
|
70
70
|
) -> list[dict[str, Any]]:
|
71
71
|
"""
|
72
72
|
Compute a sublist with images that match a filter set.
|
fractal_server/ssh/_fabric.py
CHANGED
@@ -56,6 +56,7 @@ def _acquire_lock_with_timeout(
|
|
56
56
|
"""
|
57
57
|
logger = get_logger(logger_name)
|
58
58
|
logger.info(f"Trying to acquire lock for '{label}', with {timeout=}")
|
59
|
+
t_start_lock_acquire = time.perf_counter()
|
59
60
|
result = lock.acquire(timeout=timeout)
|
60
61
|
try:
|
61
62
|
if not result:
|
@@ -64,7 +65,9 @@ def _acquire_lock_with_timeout(
|
|
64
65
|
f"Failed to acquire lock for '{label}' within "
|
65
66
|
f"{timeout} seconds"
|
66
67
|
)
|
67
|
-
|
68
|
+
t_end_lock_acquire = time.perf_counter()
|
69
|
+
elapsed = t_end_lock_acquire - t_start_lock_acquire
|
70
|
+
logger.info(f"Lock for '{label}' was acquired - {elapsed=:.4f} s")
|
68
71
|
yield result
|
69
72
|
finally:
|
70
73
|
if result:
|