fractal-server 2.12.0a1__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/__main__.py +17 -63
- 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 -23
- 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 -6
- fractal_server/app/runner/executors/slurm/ssh/executor.py +35 -50
- 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 +77 -41
- 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 +7 -4
- fractal_server/app/security/signup_email.py +21 -12
- fractal_server/config.py +123 -75
- fractal_server/images/models.py +18 -12
- fractal_server/main.py +13 -10
- fractal_server/migrations/env.py +16 -63
- fractal_server/tasks/v2/local/collect.py +9 -8
- fractal_server/tasks/v2/local/deactivate.py +3 -0
- fractal_server/tasks/v2/local/reactivate.py +3 -0
- fractal_server/tasks/v2/ssh/collect.py +8 -8
- fractal_server/tasks/v2/ssh/deactivate.py +3 -0
- fractal_server/tasks/v2/ssh/reactivate.py +9 -6
- fractal_server/tasks/v2/utils_background.py +1 -1
- fractal_server/tasks/v2/utils_database.py +1 -1
- {fractal_server-2.12.0a1.dist-info → fractal_server-2.13.0.dist-info}/METADATA +10 -11
- {fractal_server-2.12.0a1.dist-info → fractal_server-2.13.0.dist-info}/RECORD +78 -81
- 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.0a1.dist-info → fractal_server-2.13.0.dist-info}/LICENSE +0 -0
- {fractal_server-2.12.0a1.dist-info → fractal_server-2.13.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.12.0a1.dist-info → fractal_server-2.13.0.dist-info}/entry_points.txt +0 -0
@@ -3,9 +3,11 @@ from enum import Enum
|
|
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
|
8
|
+
from pydantic import field_serializer
|
9
|
+
from pydantic import field_validator
|
10
|
+
from pydantic.types import AwareDatetime
|
9
11
|
|
10
12
|
from .._validators import val_absolute_path
|
11
13
|
from .._validators import valdict_keys
|
@@ -32,7 +34,8 @@ class TaskGroupActivityActionV2(str, Enum):
|
|
32
34
|
REACTIVATE = "reactivate"
|
33
35
|
|
34
36
|
|
35
|
-
class TaskGroupCreateV2(BaseModel
|
37
|
+
class TaskGroupCreateV2(BaseModel):
|
38
|
+
model_config = ConfigDict(extra="forbid")
|
36
39
|
user_id: int
|
37
40
|
user_group_id: Optional[int] = None
|
38
41
|
active: bool = True
|
@@ -48,21 +51,21 @@ class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
|
|
48
51
|
pinned_package_versions: dict[str, str] = Field(default_factory=dict)
|
49
52
|
|
50
53
|
# Validators
|
51
|
-
_path =
|
52
|
-
_venv_path =
|
53
|
-
val_absolute_path("venv_path")
|
54
|
+
_path = field_validator("path")(classmethod(val_absolute_path("path")))
|
55
|
+
_venv_path = field_validator("venv_path")(
|
56
|
+
classmethod(val_absolute_path("venv_path"))
|
54
57
|
)
|
55
|
-
_wheel_path =
|
56
|
-
val_absolute_path("wheel_path")
|
58
|
+
_wheel_path = field_validator("wheel_path")(
|
59
|
+
classmethod(val_absolute_path("wheel_path"))
|
57
60
|
)
|
58
|
-
_pinned_package_versions =
|
59
|
-
"pinned_package_versions"
|
60
|
-
)(valdict_keys("pinned_package_versions"))
|
61
|
-
_pip_extras = validator("pip_extras", allow_reuse=True)(
|
62
|
-
valstr("pip_extras")
|
61
|
+
_pinned_package_versions = field_validator("pinned_package_versions")(
|
62
|
+
valdict_keys("pinned_package_versions")
|
63
63
|
)
|
64
|
-
|
65
|
-
valstr("
|
64
|
+
_pip_extras = field_validator("pip_extras")(
|
65
|
+
classmethod(valstr("pip_extras"))
|
66
|
+
)
|
67
|
+
_python_version = field_validator("python_version")(
|
68
|
+
classmethod(valstr("python_version"))
|
66
69
|
)
|
67
70
|
|
68
71
|
|
@@ -99,11 +102,16 @@ class TaskGroupReadV2(BaseModel):
|
|
99
102
|
venv_file_number: Optional[int] = None
|
100
103
|
|
101
104
|
active: bool
|
102
|
-
timestamp_created:
|
103
|
-
timestamp_last_used:
|
105
|
+
timestamp_created: AwareDatetime
|
106
|
+
timestamp_last_used: AwareDatetime
|
107
|
+
|
108
|
+
@field_serializer("timestamp_created", "timestamp_last_used")
|
109
|
+
def serialize_datetime(v: datetime) -> str:
|
110
|
+
return v.isoformat()
|
104
111
|
|
105
112
|
|
106
|
-
class TaskGroupUpdateV2(BaseModel
|
113
|
+
class TaskGroupUpdateV2(BaseModel):
|
114
|
+
model_config = ConfigDict(extra="forbid")
|
107
115
|
user_group_id: Optional[int] = None
|
108
116
|
|
109
117
|
|
@@ -111,10 +119,21 @@ class TaskGroupActivityV2Read(BaseModel):
|
|
111
119
|
id: int
|
112
120
|
user_id: int
|
113
121
|
taskgroupv2_id: Optional[int] = None
|
114
|
-
timestamp_started:
|
115
|
-
timestamp_ended: Optional[
|
122
|
+
timestamp_started: AwareDatetime
|
123
|
+
timestamp_ended: Optional[AwareDatetime] = None
|
116
124
|
pkg_name: str
|
117
125
|
version: str
|
118
126
|
status: TaskGroupActivityStatusV2
|
119
127
|
action: TaskGroupActivityActionV2
|
120
128
|
log: Optional[str] = None
|
129
|
+
|
130
|
+
@field_serializer("timestamp_started")
|
131
|
+
def serialize_datetime_start(v: datetime) -> str:
|
132
|
+
return v.isoformat()
|
133
|
+
|
134
|
+
@field_serializer("timestamp_ended")
|
135
|
+
def serialize_datetime_end(v: Optional[datetime]) -> Optional[str]:
|
136
|
+
if v is None:
|
137
|
+
return None
|
138
|
+
else:
|
139
|
+
return v.isoformat()
|
@@ -2,8 +2,10 @@ from datetime import datetime
|
|
2
2
|
from typing import Optional
|
3
3
|
|
4
4
|
from pydantic import BaseModel
|
5
|
-
from pydantic import
|
6
|
-
from pydantic import
|
5
|
+
from pydantic import ConfigDict
|
6
|
+
from pydantic import field_serializer
|
7
|
+
from pydantic import field_validator
|
8
|
+
from pydantic.types import AwareDatetime
|
7
9
|
|
8
10
|
from .._validators import valstr
|
9
11
|
from .project import ProjectReadV2
|
@@ -13,12 +15,14 @@ from .workflowtask import WorkflowTaskReadV2
|
|
13
15
|
from .workflowtask import WorkflowTaskReadV2WithWarning
|
14
16
|
|
15
17
|
|
16
|
-
class WorkflowCreateV2(BaseModel
|
18
|
+
class WorkflowCreateV2(BaseModel):
|
19
|
+
|
20
|
+
model_config = ConfigDict(extra="forbid")
|
17
21
|
|
18
22
|
name: str
|
19
23
|
|
20
24
|
# Validators
|
21
|
-
_name =
|
25
|
+
_name = field_validator("name")(classmethod(valstr("name")))
|
22
26
|
|
23
27
|
|
24
28
|
class WorkflowReadV2(BaseModel):
|
@@ -28,22 +32,29 @@ class WorkflowReadV2(BaseModel):
|
|
28
32
|
project_id: int
|
29
33
|
task_list: list[WorkflowTaskReadV2]
|
30
34
|
project: ProjectReadV2
|
31
|
-
timestamp_created:
|
35
|
+
timestamp_created: AwareDatetime
|
36
|
+
|
37
|
+
@field_serializer("timestamp_created")
|
38
|
+
def serialize_datetime(v: datetime) -> str:
|
39
|
+
return v.isoformat()
|
32
40
|
|
33
41
|
|
34
42
|
class WorkflowReadV2WithWarnings(WorkflowReadV2):
|
35
43
|
task_list: list[WorkflowTaskReadV2WithWarning]
|
36
44
|
|
37
45
|
|
38
|
-
class WorkflowUpdateV2(BaseModel
|
46
|
+
class WorkflowUpdateV2(BaseModel):
|
47
|
+
|
48
|
+
model_config = ConfigDict(extra="forbid")
|
39
49
|
|
40
|
-
name: Optional[str]
|
41
|
-
reordered_workflowtask_ids: Optional[list[int]]
|
50
|
+
name: Optional[str] = None
|
51
|
+
reordered_workflowtask_ids: Optional[list[int]] = None
|
42
52
|
|
43
53
|
# Validators
|
44
|
-
_name =
|
54
|
+
_name = field_validator("name")(classmethod(valstr("name")))
|
45
55
|
|
46
|
-
@
|
56
|
+
@field_validator("reordered_workflowtask_ids")
|
57
|
+
@classmethod
|
47
58
|
def check_positive_and_unique(cls, value):
|
48
59
|
if any(i < 0 for i in value):
|
49
60
|
raise ValueError("Negative `id` in `reordered_workflowtask_ids`")
|
@@ -52,7 +63,7 @@ class WorkflowUpdateV2(BaseModel, extra=Extra.forbid):
|
|
52
63
|
return value
|
53
64
|
|
54
65
|
|
55
|
-
class WorkflowImportV2(BaseModel
|
66
|
+
class WorkflowImportV2(BaseModel):
|
56
67
|
"""
|
57
68
|
Class for `Workflow` import.
|
58
69
|
|
@@ -60,11 +71,12 @@ class WorkflowImportV2(BaseModel, extra=Extra.forbid):
|
|
60
71
|
task_list:
|
61
72
|
"""
|
62
73
|
|
74
|
+
model_config = ConfigDict(extra="forbid")
|
63
75
|
name: str
|
64
76
|
task_list: list[WorkflowTaskImportV2]
|
65
77
|
|
66
78
|
# Validators
|
67
|
-
_name =
|
79
|
+
_name = field_validator("name")(classmethod(valstr("name")))
|
68
80
|
|
69
81
|
|
70
82
|
class WorkflowExportV2(BaseModel):
|
@@ -4,10 +4,10 @@ from typing import Optional
|
|
4
4
|
from typing import Union
|
5
5
|
|
6
6
|
from pydantic import BaseModel
|
7
|
-
from pydantic import
|
7
|
+
from pydantic import ConfigDict
|
8
8
|
from pydantic import Field
|
9
|
-
from pydantic import
|
10
|
-
from pydantic import
|
9
|
+
from pydantic import field_validator
|
10
|
+
from pydantic import model_validator
|
11
11
|
|
12
12
|
from .._filter_validators import validate_type_filters
|
13
13
|
from .._validators import root_validate_dict_keys
|
@@ -39,34 +39,35 @@ class WorkflowTaskStatusTypeV2(str, Enum):
|
|
39
39
|
FAILED = "failed"
|
40
40
|
|
41
41
|
|
42
|
-
class WorkflowTaskCreateV2(BaseModel
|
42
|
+
class WorkflowTaskCreateV2(BaseModel):
|
43
|
+
model_config = ConfigDict(extra="forbid")
|
43
44
|
|
44
|
-
meta_non_parallel: Optional[dict[str, Any]]
|
45
|
-
meta_parallel: Optional[dict[str, Any]]
|
46
|
-
args_non_parallel: Optional[dict[str, Any]]
|
47
|
-
args_parallel: Optional[dict[str, Any]]
|
45
|
+
meta_non_parallel: Optional[dict[str, Any]] = None
|
46
|
+
meta_parallel: Optional[dict[str, Any]] = None
|
47
|
+
args_non_parallel: Optional[dict[str, Any]] = None
|
48
|
+
args_parallel: Optional[dict[str, Any]] = None
|
48
49
|
type_filters: dict[str, bool] = Field(default_factory=dict)
|
49
50
|
|
50
51
|
# Validators
|
51
|
-
_dict_keys =
|
52
|
-
root_validate_dict_keys
|
52
|
+
_dict_keys = model_validator(mode="before")(
|
53
|
+
classmethod(root_validate_dict_keys)
|
53
54
|
)
|
54
|
-
_type_filters =
|
55
|
-
validate_type_filters
|
55
|
+
_type_filters = field_validator("type_filters")(
|
56
|
+
classmethod(validate_type_filters)
|
56
57
|
)
|
57
|
-
|
58
|
-
|
59
|
-
valdict_keys("meta_non_parallel")
|
58
|
+
_meta_non_parallel = field_validator("meta_non_parallel")(
|
59
|
+
classmethod(valdict_keys("meta_non_parallel"))
|
60
60
|
)
|
61
|
-
_meta_parallel =
|
62
|
-
valdict_keys("meta_parallel")
|
61
|
+
_meta_parallel = field_validator("meta_parallel")(
|
62
|
+
classmethod(valdict_keys("meta_parallel"))
|
63
63
|
)
|
64
64
|
|
65
|
-
@
|
65
|
+
@field_validator("args_non_parallel")
|
66
|
+
@classmethod
|
66
67
|
def validate_args_non_parallel(cls, value):
|
67
68
|
if value is None:
|
68
69
|
return
|
69
|
-
valdict_keys("args_non_parallel")(value)
|
70
|
+
valdict_keys("args_non_parallel")(cls, value)
|
70
71
|
args_keys = set(value.keys())
|
71
72
|
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
72
73
|
if intersect_keys:
|
@@ -76,11 +77,12 @@ class WorkflowTaskCreateV2(BaseModel, extra=Extra.forbid):
|
|
76
77
|
)
|
77
78
|
return value
|
78
79
|
|
79
|
-
@
|
80
|
+
@field_validator("args_parallel")
|
81
|
+
@classmethod
|
80
82
|
def validate_args_parallel(cls, value):
|
81
83
|
if value is None:
|
82
84
|
return
|
83
|
-
valdict_keys("args_parallel")(value)
|
85
|
+
valdict_keys("args_parallel")(cls, value)
|
84
86
|
args_keys = set(value.keys())
|
85
87
|
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
86
88
|
if intersect_keys:
|
@@ -99,16 +101,15 @@ class WorkflowTaskReplaceV2(BaseModel):
|
|
99
101
|
|
100
102
|
|
101
103
|
class WorkflowTaskReadV2(BaseModel):
|
102
|
-
|
103
104
|
id: int
|
104
105
|
|
105
106
|
workflow_id: int
|
106
|
-
order: Optional[int]
|
107
|
-
meta_non_parallel: Optional[dict[str, Any]]
|
108
|
-
meta_parallel: Optional[dict[str, Any]]
|
107
|
+
order: Optional[int] = None
|
108
|
+
meta_non_parallel: Optional[dict[str, Any]] = None
|
109
|
+
meta_parallel: Optional[dict[str, Any]] = None
|
109
110
|
|
110
|
-
args_non_parallel: Optional[dict[str, Any]]
|
111
|
-
args_parallel: Optional[dict[str, Any]]
|
111
|
+
args_non_parallel: Optional[dict[str, Any]] = None
|
112
|
+
args_parallel: Optional[dict[str, Any]] = None
|
112
113
|
|
113
114
|
type_filters: dict[str, bool]
|
114
115
|
|
@@ -121,34 +122,35 @@ class WorkflowTaskReadV2WithWarning(WorkflowTaskReadV2):
|
|
121
122
|
warning: Optional[str] = None
|
122
123
|
|
123
124
|
|
124
|
-
class WorkflowTaskUpdateV2(BaseModel
|
125
|
+
class WorkflowTaskUpdateV2(BaseModel):
|
126
|
+
model_config = ConfigDict(extra="forbid")
|
125
127
|
|
126
|
-
meta_non_parallel: Optional[dict[str, Any]]
|
127
|
-
meta_parallel: Optional[dict[str, Any]]
|
128
|
-
args_non_parallel: Optional[dict[str, Any]]
|
129
|
-
args_parallel: Optional[dict[str, Any]]
|
130
|
-
type_filters: Optional[dict[str, bool]]
|
128
|
+
meta_non_parallel: Optional[dict[str, Any]] = None
|
129
|
+
meta_parallel: Optional[dict[str, Any]] = None
|
130
|
+
args_non_parallel: Optional[dict[str, Any]] = None
|
131
|
+
args_parallel: Optional[dict[str, Any]] = None
|
132
|
+
type_filters: Optional[dict[str, bool]] = None
|
131
133
|
|
132
134
|
# Validators
|
133
|
-
_dict_keys =
|
134
|
-
root_validate_dict_keys
|
135
|
+
_dict_keys = model_validator(mode="before")(
|
136
|
+
classmethod(root_validate_dict_keys)
|
135
137
|
)
|
136
|
-
_type_filters =
|
137
|
-
validate_type_filters
|
138
|
+
_type_filters = field_validator("type_filters")(
|
139
|
+
classmethod(validate_type_filters)
|
138
140
|
)
|
139
|
-
|
140
|
-
|
141
|
-
valdict_keys("meta_non_parallel")
|
141
|
+
_meta_non_parallel = field_validator("meta_non_parallel")(
|
142
|
+
classmethod(valdict_keys("meta_non_parallel"))
|
142
143
|
)
|
143
|
-
_meta_parallel =
|
144
|
-
valdict_keys("meta_parallel")
|
144
|
+
_meta_parallel = field_validator("meta_parallel")(
|
145
|
+
classmethod(valdict_keys("meta_parallel"))
|
145
146
|
)
|
146
147
|
|
147
|
-
@
|
148
|
+
@field_validator("args_non_parallel")
|
149
|
+
@classmethod
|
148
150
|
def validate_args_non_parallel(cls, value):
|
149
151
|
if value is None:
|
150
152
|
return
|
151
|
-
valdict_keys("args_non_parallel")(value)
|
153
|
+
valdict_keys("args_non_parallel")(cls, value)
|
152
154
|
args_keys = set(value.keys())
|
153
155
|
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
154
156
|
if intersect_keys:
|
@@ -158,11 +160,12 @@ class WorkflowTaskUpdateV2(BaseModel, extra=Extra.forbid):
|
|
158
160
|
)
|
159
161
|
return value
|
160
162
|
|
161
|
-
@
|
163
|
+
@field_validator("args_parallel")
|
164
|
+
@classmethod
|
162
165
|
def validate_args_parallel(cls, value):
|
163
166
|
if value is None:
|
164
167
|
return
|
165
|
-
valdict_keys("args_parallel")(value)
|
168
|
+
valdict_keys("args_parallel")(cls, value)
|
166
169
|
args_keys = set(value.keys())
|
167
170
|
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
168
171
|
if intersect_keys:
|
@@ -173,7 +176,8 @@ class WorkflowTaskUpdateV2(BaseModel, extra=Extra.forbid):
|
|
173
176
|
return value
|
174
177
|
|
175
178
|
|
176
|
-
class WorkflowTaskImportV2(BaseModel
|
179
|
+
class WorkflowTaskImportV2(BaseModel):
|
180
|
+
model_config = ConfigDict(extra="forbid")
|
177
181
|
|
178
182
|
meta_non_parallel: Optional[dict[str, Any]] = None
|
179
183
|
meta_parallel: Optional[dict[str, Any]] = None
|
@@ -185,7 +189,8 @@ class WorkflowTaskImportV2(BaseModel, extra=Extra.forbid):
|
|
185
189
|
task: Union[TaskImportV2, TaskImportV2Legacy]
|
186
190
|
|
187
191
|
# Validators
|
188
|
-
@
|
192
|
+
@model_validator(mode="before")
|
193
|
+
@classmethod
|
189
194
|
def update_legacy_filters(cls, values: dict):
|
190
195
|
"""
|
191
196
|
Transform legacy filters (created with fractal-server<2.11.0)
|
@@ -197,7 +202,6 @@ class WorkflowTaskImportV2(BaseModel, extra=Extra.forbid):
|
|
197
202
|
"Cannot set filters both through the legacy field "
|
198
203
|
"('filters') and the new one ('type_filters')."
|
199
204
|
)
|
200
|
-
|
201
205
|
else:
|
202
206
|
# As of 2.11.0, WorkflowTask do not have attribute filters
|
203
207
|
# any more.
|
@@ -213,26 +217,24 @@ class WorkflowTaskImportV2(BaseModel, extra=Extra.forbid):
|
|
213
217
|
|
214
218
|
return values
|
215
219
|
|
216
|
-
_type_filters =
|
217
|
-
validate_type_filters
|
220
|
+
_type_filters = field_validator("type_filters")(
|
221
|
+
classmethod(validate_type_filters)
|
218
222
|
)
|
219
|
-
|
220
|
-
|
221
|
-
valdict_keys("meta_non_parallel")
|
223
|
+
_meta_non_parallel = field_validator("meta_non_parallel")(
|
224
|
+
classmethod(valdict_keys("meta_non_parallel"))
|
222
225
|
)
|
223
|
-
_meta_parallel =
|
224
|
-
valdict_keys("meta_parallel")
|
226
|
+
_meta_parallel = field_validator("meta_parallel")(
|
227
|
+
classmethod(valdict_keys("meta_parallel"))
|
225
228
|
)
|
226
|
-
_args_non_parallel =
|
227
|
-
valdict_keys("args_non_parallel")
|
229
|
+
_args_non_parallel = field_validator("args_non_parallel")(
|
230
|
+
classmethod(valdict_keys("args_non_parallel"))
|
228
231
|
)
|
229
|
-
_args_parallel =
|
230
|
-
valdict_keys("args_parallel")
|
232
|
+
_args_parallel = field_validator("args_parallel")(
|
233
|
+
classmethod(valdict_keys("args_parallel"))
|
231
234
|
)
|
232
235
|
|
233
236
|
|
234
237
|
class WorkflowTaskExportV2(BaseModel):
|
235
|
-
|
236
238
|
meta_non_parallel: Optional[dict[str, Any]] = None
|
237
239
|
meta_parallel: Optional[dict[str, Any]] = None
|
238
240
|
args_non_parallel: Optional[dict[str, Any]] = None
|
@@ -83,7 +83,7 @@ class SQLModelUserDatabaseAsync(Generic[UP, ID], BaseUserDatabase[UP, ID]):
|
|
83
83
|
|
84
84
|
session: AsyncSession
|
85
85
|
user_model: Type[UP]
|
86
|
-
oauth_account_model: Optional[Type[OAuthAccount]]
|
86
|
+
oauth_account_model: Optional[Type[OAuthAccount]] = None
|
87
87
|
|
88
88
|
def __init__(
|
89
89
|
self,
|
@@ -252,15 +252,18 @@ class UserManager(IntegerIDMixin, BaseUserManager[UserOAuth, int]):
|
|
252
252
|
# Send mail section
|
253
253
|
settings = Inject(get_settings)
|
254
254
|
|
255
|
-
if
|
255
|
+
if (
|
256
|
+
this_user.oauth_accounts
|
257
|
+
and settings.email_settings is not None
|
258
|
+
):
|
256
259
|
try:
|
257
260
|
logger.info(
|
258
261
|
"START sending email about new signup to "
|
259
|
-
f"{settings.
|
262
|
+
f"{settings.email_settings.recipients}."
|
260
263
|
)
|
261
264
|
mail_new_oauth_signup(
|
262
265
|
msg=f"New user registered: '{this_user.email}'.",
|
263
|
-
|
266
|
+
email_settings=settings.email_settings,
|
264
267
|
)
|
265
268
|
logger.info("END sending email about new signup.")
|
266
269
|
except Exception as e:
|
@@ -2,38 +2,47 @@ import smtplib
|
|
2
2
|
from email.message import EmailMessage
|
3
3
|
from email.utils import formataddr
|
4
4
|
|
5
|
+
from cryptography.fernet import Fernet
|
6
|
+
|
5
7
|
from fractal_server.config import MailSettings
|
6
8
|
|
7
9
|
|
8
|
-
def mail_new_oauth_signup(msg: str,
|
10
|
+
def mail_new_oauth_signup(msg: str, email_settings: MailSettings):
|
9
11
|
"""
|
10
12
|
Send an email using the specified settings to notify a new OAuth signup.
|
11
13
|
"""
|
12
14
|
|
13
15
|
mail_msg = EmailMessage()
|
14
16
|
mail_msg.set_content(msg)
|
15
|
-
mail_msg["From"] = formataddr(
|
17
|
+
mail_msg["From"] = formataddr(
|
18
|
+
(email_settings.sender, email_settings.sender)
|
19
|
+
)
|
16
20
|
mail_msg["To"] = ", ".join(
|
17
21
|
[
|
18
22
|
formataddr((recipient, recipient))
|
19
|
-
for recipient in
|
23
|
+
for recipient in email_settings.recipients
|
20
24
|
]
|
21
25
|
)
|
22
26
|
mail_msg[
|
23
27
|
"Subject"
|
24
|
-
] = f"[Fractal, {
|
28
|
+
] = f"[Fractal, {email_settings.instance_name}] New OAuth signup"
|
25
29
|
|
26
|
-
with smtplib.SMTP(
|
30
|
+
with smtplib.SMTP(
|
31
|
+
email_settings.smtp_server, email_settings.port
|
32
|
+
) as server:
|
27
33
|
server.ehlo()
|
28
|
-
if
|
34
|
+
if email_settings.use_starttls:
|
29
35
|
server.starttls()
|
30
36
|
server.ehlo()
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
37
|
+
if email_settings.use_login:
|
38
|
+
password = (
|
39
|
+
Fernet(email_settings.encryption_key)
|
40
|
+
.decrypt(email_settings.encrypted_password)
|
41
|
+
.decode("utf-8")
|
42
|
+
)
|
43
|
+
server.login(user=email_settings.sender, password=password)
|
35
44
|
server.sendmail(
|
36
|
-
from_addr=
|
37
|
-
to_addrs=
|
45
|
+
from_addr=email_settings.sender,
|
46
|
+
to_addrs=email_settings.recipients,
|
38
47
|
msg=mail_msg.as_string(),
|
39
48
|
)
|