fractal-server 2.16.6__py3-none-any.whl → 2.17.0a0__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 +129 -22
- fractal_server/app/db/__init__.py +9 -11
- fractal_server/app/models/security.py +7 -3
- fractal_server/app/models/user_settings.py +0 -4
- fractal_server/app/models/v2/__init__.py +4 -0
- fractal_server/app/models/v2/profile.py +16 -0
- fractal_server/app/models/v2/project.py +3 -0
- fractal_server/app/models/v2/resource.py +130 -0
- fractal_server/app/models/v2/task_group.py +3 -0
- fractal_server/app/routes/admin/v2/__init__.py +4 -0
- fractal_server/app/routes/admin/v2/_aux_functions.py +55 -0
- fractal_server/app/routes/admin/v2/profile.py +86 -0
- fractal_server/app/routes/admin/v2/resource.py +229 -0
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +48 -82
- fractal_server/app/routes/api/__init__.py +26 -7
- fractal_server/app/routes/api/v2/_aux_functions.py +27 -1
- fractal_server/app/routes/api/v2/_aux_functions_history.py +2 -2
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +7 -7
- fractal_server/app/routes/api/v2/project.py +5 -1
- fractal_server/app/routes/api/v2/submit.py +32 -24
- fractal_server/app/routes/api/v2/task.py +5 -0
- fractal_server/app/routes/api/v2/task_collection.py +36 -47
- fractal_server/app/routes/api/v2/task_collection_custom.py +11 -5
- fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -40
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +39 -82
- fractal_server/app/routes/auth/_aux_auth.py +3 -3
- fractal_server/app/routes/auth/current_user.py +45 -7
- fractal_server/app/routes/auth/oauth.py +1 -1
- fractal_server/app/routes/auth/users.py +9 -0
- fractal_server/app/routes/aux/_runner.py +2 -1
- fractal_server/app/routes/aux/validate_user_profile.py +62 -0
- fractal_server/app/routes/aux/validate_user_settings.py +12 -9
- fractal_server/app/schemas/user.py +20 -13
- fractal_server/app/schemas/user_settings.py +0 -4
- fractal_server/app/schemas/v2/__init__.py +11 -0
- fractal_server/app/schemas/v2/profile.py +72 -0
- fractal_server/app/schemas/v2/resource.py +117 -0
- fractal_server/app/security/__init__.py +6 -13
- fractal_server/app/security/signup_email.py +2 -2
- fractal_server/app/user_settings.py +2 -12
- fractal_server/config/__init__.py +23 -0
- fractal_server/config/_database.py +58 -0
- fractal_server/config/_email.py +170 -0
- fractal_server/config/_init_data.py +27 -0
- fractal_server/config/_main.py +216 -0
- fractal_server/config/_settings_config.py +7 -0
- fractal_server/images/tools.py +3 -3
- fractal_server/logger.py +3 -3
- fractal_server/main.py +14 -21
- fractal_server/migrations/versions/90f6508c6379_drop_useroauth_username.py +36 -0
- fractal_server/migrations/versions/a80ac5a352bf_resource_profile.py +195 -0
- fractal_server/runner/config/__init__.py +2 -0
- fractal_server/runner/config/_local.py +21 -0
- fractal_server/runner/config/_slurm.py +128 -0
- fractal_server/runner/config/slurm_mem_to_MB.py +63 -0
- fractal_server/runner/exceptions.py +4 -0
- fractal_server/runner/executors/base_runner.py +17 -7
- fractal_server/runner/executors/local/get_local_config.py +21 -86
- fractal_server/runner/executors/local/runner.py +48 -5
- fractal_server/runner/executors/slurm_common/_batching.py +2 -2
- fractal_server/runner/executors/slurm_common/base_slurm_runner.py +59 -25
- fractal_server/runner/executors/slurm_common/get_slurm_config.py +38 -54
- fractal_server/runner/executors/slurm_common/remote.py +1 -1
- fractal_server/runner/executors/slurm_common/{_slurm_config.py → slurm_config.py} +3 -254
- fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -1
- fractal_server/runner/executors/slurm_ssh/runner.py +12 -14
- fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -2
- fractal_server/runner/executors/slurm_sudo/runner.py +12 -12
- fractal_server/runner/v2/_local.py +36 -21
- fractal_server/runner/v2/_slurm_ssh.py +40 -4
- fractal_server/runner/v2/_slurm_sudo.py +41 -11
- fractal_server/runner/v2/db_tools.py +1 -1
- fractal_server/runner/v2/runner.py +3 -11
- fractal_server/runner/v2/runner_functions.py +42 -28
- fractal_server/runner/v2/submit_workflow.py +87 -108
- fractal_server/runner/versions.py +8 -3
- fractal_server/ssh/_fabric.py +6 -6
- fractal_server/tasks/config/__init__.py +3 -0
- fractal_server/tasks/config/_pixi.py +127 -0
- fractal_server/tasks/config/_python.py +51 -0
- fractal_server/tasks/v2/local/_utils.py +7 -7
- fractal_server/tasks/v2/local/collect.py +13 -5
- fractal_server/tasks/v2/local/collect_pixi.py +26 -10
- fractal_server/tasks/v2/local/deactivate.py +7 -1
- fractal_server/tasks/v2/local/deactivate_pixi.py +5 -1
- fractal_server/tasks/v2/local/delete.py +4 -0
- fractal_server/tasks/v2/local/reactivate.py +13 -5
- fractal_server/tasks/v2/local/reactivate_pixi.py +27 -9
- fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +11 -10
- fractal_server/tasks/v2/ssh/_utils.py +6 -7
- fractal_server/tasks/v2/ssh/collect.py +19 -12
- fractal_server/tasks/v2/ssh/collect_pixi.py +34 -16
- fractal_server/tasks/v2/ssh/deactivate.py +12 -8
- fractal_server/tasks/v2/ssh/deactivate_pixi.py +14 -10
- fractal_server/tasks/v2/ssh/delete.py +12 -9
- fractal_server/tasks/v2/ssh/reactivate.py +18 -12
- fractal_server/tasks/v2/ssh/reactivate_pixi.py +36 -17
- fractal_server/tasks/v2/templates/4_pip_show.sh +4 -6
- fractal_server/tasks/v2/utils_database.py +2 -2
- fractal_server/tasks/v2/utils_python_interpreter.py +8 -16
- fractal_server/tasks/v2/utils_templates.py +7 -10
- fractal_server/utils.py +1 -1
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/METADATA +1 -1
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/RECORD +110 -88
- fractal_server/config.py +0 -906
- /fractal_server/{runner → app}/shutdown.py +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/WHEEL +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
from typing import Any
|
|
4
|
+
from typing import Literal
|
|
5
|
+
from typing import Self
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from pydantic import Discriminator
|
|
9
|
+
from pydantic import model_validator
|
|
10
|
+
from pydantic import Tag
|
|
11
|
+
from pydantic import validate_call
|
|
12
|
+
from pydantic.types import AwareDatetime
|
|
13
|
+
|
|
14
|
+
from fractal_server.runner.config import JobRunnerConfigLocal
|
|
15
|
+
from fractal_server.runner.config import JobRunnerConfigSLURM
|
|
16
|
+
from fractal_server.tasks.config import TasksPixiSettings
|
|
17
|
+
from fractal_server.tasks.config import TasksPythonSettings
|
|
18
|
+
from fractal_server.types import AbsolutePathStr
|
|
19
|
+
from fractal_server.types import NonEmptyStr
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ResourceType(StrEnum):
|
|
23
|
+
SLURM_SUDO = "slurm_sudo"
|
|
24
|
+
SLURM_SSH = "slurm_ssh"
|
|
25
|
+
LOCAL = "local"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _ValidResourceBase(BaseModel):
|
|
29
|
+
type: ResourceType
|
|
30
|
+
name: NonEmptyStr
|
|
31
|
+
|
|
32
|
+
# Tasks
|
|
33
|
+
tasks_python_config: TasksPythonSettings
|
|
34
|
+
tasks_pixi_config: dict[NonEmptyStr, Any]
|
|
35
|
+
tasks_local_dir: AbsolutePathStr
|
|
36
|
+
|
|
37
|
+
# Jobs
|
|
38
|
+
jobs_local_dir: AbsolutePathStr
|
|
39
|
+
jobs_runner_config: dict[NonEmptyStr, Any]
|
|
40
|
+
jobs_poll_interval: int = 5
|
|
41
|
+
|
|
42
|
+
@model_validator(mode="after")
|
|
43
|
+
def _tasks_configurations(self) -> Self:
|
|
44
|
+
if self.tasks_pixi_config != {}:
|
|
45
|
+
pixi_settings = TasksPixiSettings(**self.tasks_pixi_config)
|
|
46
|
+
if (
|
|
47
|
+
self.type == ResourceType.SLURM_SSH
|
|
48
|
+
and pixi_settings.SLURM_CONFIG is None
|
|
49
|
+
):
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"`tasks_pixi_config` must include `SLURM_CONFIG`."
|
|
52
|
+
)
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ValidResourceLocal(_ValidResourceBase):
|
|
57
|
+
type: Literal[ResourceType.LOCAL]
|
|
58
|
+
jobs_runner_config: JobRunnerConfigLocal
|
|
59
|
+
jobs_slurm_python_worker: None = None
|
|
60
|
+
host: None = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ValidResourceSlurmSudo(_ValidResourceBase):
|
|
64
|
+
type: Literal[ResourceType.SLURM_SUDO]
|
|
65
|
+
jobs_slurm_python_worker: AbsolutePathStr
|
|
66
|
+
jobs_runner_config: JobRunnerConfigSLURM
|
|
67
|
+
host: None = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ValidResourceSlurmSSH(_ValidResourceBase):
|
|
71
|
+
type: Literal[ResourceType.SLURM_SSH]
|
|
72
|
+
host: NonEmptyStr
|
|
73
|
+
jobs_slurm_python_worker: AbsolutePathStr
|
|
74
|
+
jobs_runner_config: JobRunnerConfigSLURM
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_discriminator_value(v: Any) -> str:
|
|
78
|
+
# See https://github.com/fastapi/fastapi/discussions/12941
|
|
79
|
+
if isinstance(v, dict):
|
|
80
|
+
return v.get("type", None)
|
|
81
|
+
return getattr(v, "type", None)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
ResourceCreate = Annotated[
|
|
85
|
+
Annotated[ValidResourceLocal, Tag(ResourceType.LOCAL)]
|
|
86
|
+
| Annotated[ValidResourceSlurmSudo, Tag(ResourceType.SLURM_SUDO)]
|
|
87
|
+
| Annotated[ValidResourceSlurmSSH, Tag(ResourceType.SLURM_SSH)],
|
|
88
|
+
Discriminator(get_discriminator_value),
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ResourceRead(BaseModel):
|
|
93
|
+
id: int
|
|
94
|
+
|
|
95
|
+
type: str
|
|
96
|
+
|
|
97
|
+
name: str
|
|
98
|
+
timestamp_created: AwareDatetime
|
|
99
|
+
|
|
100
|
+
host: str | None
|
|
101
|
+
|
|
102
|
+
jobs_local_dir: str
|
|
103
|
+
jobs_runner_config: dict[str, Any]
|
|
104
|
+
jobs_slurm_python_worker: str | None
|
|
105
|
+
jobs_poll_interval: int
|
|
106
|
+
|
|
107
|
+
tasks_local_dir: str
|
|
108
|
+
tasks_python_config: dict[str, Any]
|
|
109
|
+
tasks_pixi_config: dict[str, Any]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@validate_call
|
|
113
|
+
def validate_resource_data(_data: ResourceCreate):
|
|
114
|
+
"""
|
|
115
|
+
We use `@validate_call` because `ResourceCreate` is a `Union` type and it
|
|
116
|
+
cannot be instantiated directly.
|
|
117
|
+
"""
|
|
@@ -58,7 +58,7 @@ from fractal_server.app.models import UserOAuth
|
|
|
58
58
|
from fractal_server.app.models import UserSettings
|
|
59
59
|
from fractal_server.app.schemas.user import UserCreate
|
|
60
60
|
from fractal_server.app.security.signup_email import mail_new_oauth_signup
|
|
61
|
-
from fractal_server.config import
|
|
61
|
+
from fractal_server.config import get_email_settings
|
|
62
62
|
from fractal_server.logger import set_logger
|
|
63
63
|
from fractal_server.syringe import Inject
|
|
64
64
|
|
|
@@ -248,20 +248,17 @@ class UserManager(IntegerIDMixin, BaseUserManager[UserOAuth, int]):
|
|
|
248
248
|
)
|
|
249
249
|
|
|
250
250
|
# Send mail section
|
|
251
|
-
|
|
251
|
+
email_settings = Inject(get_email_settings)
|
|
252
252
|
|
|
253
|
-
if
|
|
254
|
-
this_user.oauth_accounts
|
|
255
|
-
and settings.email_settings is not None
|
|
256
|
-
):
|
|
253
|
+
if this_user.oauth_accounts and email_settings.public is not None:
|
|
257
254
|
try:
|
|
258
255
|
logger.info(
|
|
259
256
|
"START sending email about new signup to "
|
|
260
|
-
f"{
|
|
257
|
+
f"{email_settings.public.recipients}."
|
|
261
258
|
)
|
|
262
259
|
mail_new_oauth_signup(
|
|
263
260
|
msg=f"New user registered: '{this_user.email}'.",
|
|
264
|
-
email_settings=
|
|
261
|
+
email_settings=email_settings.public,
|
|
265
262
|
)
|
|
266
263
|
logger.info("END sending email about new signup.")
|
|
267
264
|
except Exception as e:
|
|
@@ -288,7 +285,6 @@ async def _create_first_user(
|
|
|
288
285
|
password: str,
|
|
289
286
|
is_superuser: bool = False,
|
|
290
287
|
is_verified: bool = False,
|
|
291
|
-
username: str | None = None,
|
|
292
288
|
) -> None:
|
|
293
289
|
"""
|
|
294
290
|
Private method to create the first fractal-server user
|
|
@@ -306,12 +302,11 @@ async def _create_first_user(
|
|
|
306
302
|
See [fastapi_users docs](https://fastapi-users.github.io/fastapi-users/
|
|
307
303
|
12.1/cookbook/create-user-programmatically)
|
|
308
304
|
|
|
309
|
-
|
|
305
|
+
Args:
|
|
310
306
|
email: New user's email
|
|
311
307
|
password: New user's password
|
|
312
308
|
is_superuser: `True` if the new user is a superuser
|
|
313
309
|
is_verified: `True` if the new user is verified
|
|
314
|
-
username:
|
|
315
310
|
"""
|
|
316
311
|
function_logger = set_logger("fractal_server.create_first_user")
|
|
317
312
|
function_logger.info(f"START _create_first_user, with email '{email}'")
|
|
@@ -339,8 +334,6 @@ async def _create_first_user(
|
|
|
339
334
|
is_superuser=is_superuser,
|
|
340
335
|
is_verified=is_verified,
|
|
341
336
|
)
|
|
342
|
-
if username is not None:
|
|
343
|
-
kwargs["username"] = username
|
|
344
337
|
user = await user_manager.create(UserCreate(**kwargs))
|
|
345
338
|
function_logger.info(f"User '{user.email}' created")
|
|
346
339
|
except UserAlreadyExists:
|
|
@@ -4,10 +4,10 @@ from email.utils import formataddr
|
|
|
4
4
|
|
|
5
5
|
from cryptography.fernet import Fernet
|
|
6
6
|
|
|
7
|
-
from fractal_server.config import
|
|
7
|
+
from fractal_server.config import PublicEmailSettings
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def mail_new_oauth_signup(msg: str, email_settings:
|
|
10
|
+
def mail_new_oauth_signup(msg: str, email_settings: PublicEmailSettings):
|
|
11
11
|
"""
|
|
12
12
|
Send an email using the specified settings to notify a new OAuth signup.
|
|
13
13
|
"""
|
|
@@ -8,20 +8,12 @@ class SlurmSshUserSettings(BaseModel):
|
|
|
8
8
|
execution when using the Slurm-SSH runner.
|
|
9
9
|
|
|
10
10
|
Attributes:
|
|
11
|
-
|
|
12
|
-
ssh_username: User on `ssh_host`.
|
|
13
|
-
ssh_private_key_path: Path of private SSH key for `ssh_username`.
|
|
14
|
-
ssh_tasks_dir: Task-venvs base folder on `ssh_host`.
|
|
15
|
-
ssh_jobs_dir: Jobs base folder on `ssh_host`.
|
|
11
|
+
project_dir: Folder where `slurm_user` can write.
|
|
16
12
|
slurm_accounts:
|
|
17
13
|
List of SLURM accounts, to be used upon Fractal job submission.
|
|
18
14
|
"""
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
ssh_username: str
|
|
22
|
-
ssh_private_key_path: str
|
|
23
|
-
ssh_tasks_dir: str
|
|
24
|
-
ssh_jobs_dir: str
|
|
16
|
+
project_dir: str
|
|
25
17
|
slurm_accounts: list[str]
|
|
26
18
|
|
|
27
19
|
|
|
@@ -31,12 +23,10 @@ class SlurmSudoUserSettings(BaseModel):
|
|
|
31
23
|
execution when using the Slurm-sudo runner.
|
|
32
24
|
|
|
33
25
|
Attributes:
|
|
34
|
-
slurm_user: User to be impersonated via `sudo -u`.
|
|
35
26
|
project_dir: Folder where `slurm_user` can write.
|
|
36
27
|
slurm_accounts:
|
|
37
28
|
List of SLURM accounts, to be used upon Fractal job submission.
|
|
38
29
|
"""
|
|
39
30
|
|
|
40
|
-
slurm_user: str
|
|
41
31
|
project_dir: str
|
|
42
32
|
slurm_accounts: list[str]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from ._database import DatabaseSettings
|
|
2
|
+
from ._email import EmailSettings
|
|
3
|
+
from ._email import PublicEmailSettings # noqa F401
|
|
4
|
+
from ._init_data import InitDataSettings
|
|
5
|
+
from ._main import Settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_db_settings(db_settings=DatabaseSettings()) -> DatabaseSettings:
|
|
9
|
+
return db_settings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_settings(settings=Settings()) -> Settings:
|
|
13
|
+
return settings
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_email_settings(email_settings=EmailSettings()) -> EmailSettings:
|
|
17
|
+
return email_settings
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_init_data_settings(
|
|
21
|
+
init_data_settings=InitDataSettings(),
|
|
22
|
+
) -> InitDataSettings:
|
|
23
|
+
return init_data_settings
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from pydantic import SecretStr
|
|
2
|
+
from pydantic_settings import BaseSettings
|
|
3
|
+
from pydantic_settings import SettingsConfigDict
|
|
4
|
+
from sqlalchemy.engine import URL
|
|
5
|
+
|
|
6
|
+
from ._settings_config import SETTINGS_CONFIG_DICT
|
|
7
|
+
from fractal_server.types import NonEmptyStr
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DatabaseSettings(BaseSettings):
|
|
11
|
+
"""
|
|
12
|
+
Minimal set of configurations needed for operating on the database (e.g
|
|
13
|
+
for schema migrations).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
model_config = SettingsConfigDict(**SETTINGS_CONFIG_DICT)
|
|
17
|
+
|
|
18
|
+
DB_ECHO: bool = False
|
|
19
|
+
"""
|
|
20
|
+
If `True`, make database operations verbose.
|
|
21
|
+
"""
|
|
22
|
+
POSTGRES_USER: NonEmptyStr | None = None
|
|
23
|
+
"""
|
|
24
|
+
User to use when connecting to the PostgreSQL database.
|
|
25
|
+
"""
|
|
26
|
+
POSTGRES_PASSWORD: SecretStr | None = None
|
|
27
|
+
"""
|
|
28
|
+
Password to use when connecting to the PostgreSQL database.
|
|
29
|
+
"""
|
|
30
|
+
POSTGRES_HOST: NonEmptyStr | None = "localhost"
|
|
31
|
+
"""
|
|
32
|
+
URL to the PostgreSQL server or path to a UNIX domain socket.
|
|
33
|
+
"""
|
|
34
|
+
POSTGRES_PORT: NonEmptyStr | None = "5432"
|
|
35
|
+
"""
|
|
36
|
+
Port number to use when connecting to the PostgreSQL server.
|
|
37
|
+
"""
|
|
38
|
+
POSTGRES_DB: NonEmptyStr
|
|
39
|
+
"""
|
|
40
|
+
Name of the PostgreSQL database to connect to.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def DATABASE_URL(self) -> URL:
|
|
45
|
+
if self.POSTGRES_PASSWORD is None:
|
|
46
|
+
password = None
|
|
47
|
+
else:
|
|
48
|
+
password = self.POSTGRES_PASSWORD.get_secret_value()
|
|
49
|
+
|
|
50
|
+
url = URL.create(
|
|
51
|
+
drivername="postgresql+psycopg",
|
|
52
|
+
username=self.POSTGRES_USER,
|
|
53
|
+
password=password,
|
|
54
|
+
host=self.POSTGRES_HOST,
|
|
55
|
+
port=self.POSTGRES_PORT,
|
|
56
|
+
database=self.POSTGRES_DB,
|
|
57
|
+
)
|
|
58
|
+
return url
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from cryptography.fernet import Fernet
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from pydantic import EmailStr
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
from pydantic import model_validator
|
|
8
|
+
from pydantic import SecretStr
|
|
9
|
+
from pydantic_settings import BaseSettings
|
|
10
|
+
from pydantic_settings import SettingsConfigDict
|
|
11
|
+
|
|
12
|
+
from ._settings_config import SETTINGS_CONFIG_DICT
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PublicEmailSettings(BaseModel):
|
|
16
|
+
"""
|
|
17
|
+
Schema for `EmailSettings.public`, namely the ready-to-use settings.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
sender: Sender email address
|
|
21
|
+
recipients: List of recipients email address
|
|
22
|
+
smtp_server: SMTP server address
|
|
23
|
+
port: SMTP server port
|
|
24
|
+
password: Sender password
|
|
25
|
+
instance_name: Name of SMTP server instance
|
|
26
|
+
use_starttls: Whether to use the security protocol
|
|
27
|
+
use_login: Whether to use login
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
sender: EmailStr
|
|
31
|
+
recipients: list[EmailStr] = Field(min_length=1)
|
|
32
|
+
smtp_server: str
|
|
33
|
+
port: int
|
|
34
|
+
encrypted_password: SecretStr | None = None
|
|
35
|
+
encryption_key: SecretStr | None = None
|
|
36
|
+
instance_name: str
|
|
37
|
+
use_starttls: bool
|
|
38
|
+
use_login: bool
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class EmailSettings(BaseSettings):
|
|
42
|
+
"""
|
|
43
|
+
Class with settings for email-sending feature.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
model_config = SettingsConfigDict(**SETTINGS_CONFIG_DICT)
|
|
47
|
+
|
|
48
|
+
FRACTAL_EMAIL_SENDER: EmailStr | None = None
|
|
49
|
+
"""
|
|
50
|
+
Address of the OAuth-signup email sender.
|
|
51
|
+
"""
|
|
52
|
+
FRACTAL_EMAIL_PASSWORD: SecretStr | None = None
|
|
53
|
+
"""
|
|
54
|
+
Password for the OAuth-signup email sender.
|
|
55
|
+
"""
|
|
56
|
+
FRACTAL_EMAIL_PASSWORD_KEY: SecretStr | None = None
|
|
57
|
+
"""
|
|
58
|
+
Key value for `cryptography.fernet` decrypt
|
|
59
|
+
"""
|
|
60
|
+
FRACTAL_EMAIL_SMTP_SERVER: str | None = None
|
|
61
|
+
"""
|
|
62
|
+
SMTP server for the OAuth-signup emails.
|
|
63
|
+
"""
|
|
64
|
+
FRACTAL_EMAIL_SMTP_PORT: int | None = None
|
|
65
|
+
"""
|
|
66
|
+
SMTP server port for the OAuth-signup emails.
|
|
67
|
+
"""
|
|
68
|
+
FRACTAL_EMAIL_INSTANCE_NAME: str | None = None
|
|
69
|
+
"""
|
|
70
|
+
Fractal instance name, to be included in the OAuth-signup emails.
|
|
71
|
+
"""
|
|
72
|
+
FRACTAL_EMAIL_RECIPIENTS: str | None = None
|
|
73
|
+
"""
|
|
74
|
+
Comma-separated list of recipients of the OAuth-signup emails.
|
|
75
|
+
"""
|
|
76
|
+
FRACTAL_EMAIL_USE_STARTTLS: Literal["true", "false"] = "true"
|
|
77
|
+
"""
|
|
78
|
+
Whether to use StartTLS when using the SMTP server.
|
|
79
|
+
Accepted values: 'true', 'false'.
|
|
80
|
+
"""
|
|
81
|
+
FRACTAL_EMAIL_USE_LOGIN: Literal["true", "false"] = "true"
|
|
82
|
+
"""
|
|
83
|
+
Whether to use login when using the SMTP server.
|
|
84
|
+
If 'true', FRACTAL_EMAIL_PASSWORD and FRACTAL_EMAIL_PASSWORD_KEY must be
|
|
85
|
+
provided.
|
|
86
|
+
Accepted values: 'true', 'false'.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
public: PublicEmailSettings | None = None
|
|
90
|
+
"""
|
|
91
|
+
The validated field which is actually used in `fractal-server
|
|
92
|
+
(automatically populated upon creation).
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
@model_validator(mode="after")
|
|
96
|
+
def validate_email_settings(self):
|
|
97
|
+
"""
|
|
98
|
+
Set `self.public`.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
email_values = [
|
|
102
|
+
self.FRACTAL_EMAIL_SENDER,
|
|
103
|
+
self.FRACTAL_EMAIL_SMTP_SERVER,
|
|
104
|
+
self.FRACTAL_EMAIL_SMTP_PORT,
|
|
105
|
+
self.FRACTAL_EMAIL_INSTANCE_NAME,
|
|
106
|
+
self.FRACTAL_EMAIL_RECIPIENTS,
|
|
107
|
+
]
|
|
108
|
+
if len(set(email_values)) == 1:
|
|
109
|
+
# All required EMAIL attributes are None
|
|
110
|
+
pass
|
|
111
|
+
elif None in email_values:
|
|
112
|
+
# Not all required EMAIL attributes are set
|
|
113
|
+
error_msg = (
|
|
114
|
+
"Invalid FRACTAL_EMAIL configuration. "
|
|
115
|
+
f"Given values: {email_values}."
|
|
116
|
+
)
|
|
117
|
+
raise ValueError(error_msg)
|
|
118
|
+
else:
|
|
119
|
+
use_starttls = self.FRACTAL_EMAIL_USE_STARTTLS == "true"
|
|
120
|
+
use_login = self.FRACTAL_EMAIL_USE_LOGIN == "true"
|
|
121
|
+
|
|
122
|
+
if use_login:
|
|
123
|
+
if self.FRACTAL_EMAIL_PASSWORD is None:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
"'FRACTAL_EMAIL_USE_LOGIN' is 'true' but "
|
|
126
|
+
"'FRACTAL_EMAIL_PASSWORD' is not provided."
|
|
127
|
+
)
|
|
128
|
+
if self.FRACTAL_EMAIL_PASSWORD_KEY is None:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
"'FRACTAL_EMAIL_USE_LOGIN' is 'true' but "
|
|
131
|
+
"'FRACTAL_EMAIL_PASSWORD_KEY' is not provided."
|
|
132
|
+
)
|
|
133
|
+
try:
|
|
134
|
+
(
|
|
135
|
+
Fernet(
|
|
136
|
+
self.FRACTAL_EMAIL_PASSWORD_KEY.get_secret_value()
|
|
137
|
+
)
|
|
138
|
+
.decrypt(
|
|
139
|
+
self.FRACTAL_EMAIL_PASSWORD.get_secret_value()
|
|
140
|
+
)
|
|
141
|
+
.decode("utf-8")
|
|
142
|
+
)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
"Invalid pair (FRACTAL_EMAIL_PASSWORD, "
|
|
146
|
+
"FRACTAL_EMAIL_PASSWORD_KEY). "
|
|
147
|
+
f"Original error: {str(e)}."
|
|
148
|
+
)
|
|
149
|
+
password = self.FRACTAL_EMAIL_PASSWORD.get_secret_value()
|
|
150
|
+
else:
|
|
151
|
+
password = None
|
|
152
|
+
|
|
153
|
+
if self.FRACTAL_EMAIL_PASSWORD_KEY is not None:
|
|
154
|
+
key = self.FRACTAL_EMAIL_PASSWORD_KEY.get_secret_value()
|
|
155
|
+
else:
|
|
156
|
+
key = None
|
|
157
|
+
|
|
158
|
+
self.public = PublicEmailSettings(
|
|
159
|
+
sender=self.FRACTAL_EMAIL_SENDER,
|
|
160
|
+
recipients=self.FRACTAL_EMAIL_RECIPIENTS.split(","),
|
|
161
|
+
smtp_server=self.FRACTAL_EMAIL_SMTP_SERVER,
|
|
162
|
+
port=self.FRACTAL_EMAIL_SMTP_PORT,
|
|
163
|
+
encrypted_password=password,
|
|
164
|
+
encryption_key=key,
|
|
165
|
+
instance_name=self.FRACTAL_EMAIL_INSTANCE_NAME,
|
|
166
|
+
use_starttls=use_starttls,
|
|
167
|
+
use_login=use_login,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return self
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pydantic import SecretStr
|
|
2
|
+
from pydantic_settings import BaseSettings
|
|
3
|
+
from pydantic_settings import SettingsConfigDict
|
|
4
|
+
|
|
5
|
+
from ._settings_config import SETTINGS_CONFIG_DICT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InitDataSettings(BaseSettings):
|
|
9
|
+
model_config = SettingsConfigDict(**SETTINGS_CONFIG_DICT)
|
|
10
|
+
|
|
11
|
+
FRACTAL_DEFAULT_ADMIN_EMAIL: str = "admin@fractal.xy"
|
|
12
|
+
"""
|
|
13
|
+
Admin default email, used upon creation of the first superuser during
|
|
14
|
+
server startup.
|
|
15
|
+
|
|
16
|
+
⚠️ **IMPORTANT**: After the server startup, you should always edit the
|
|
17
|
+
default admin credentials.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
FRACTAL_DEFAULT_ADMIN_PASSWORD: SecretStr = "1234"
|
|
21
|
+
"""
|
|
22
|
+
Admin default password, used upon creation of the first superuser during
|
|
23
|
+
server startup.
|
|
24
|
+
|
|
25
|
+
⚠️ **IMPORTANT**: After the server startup, you should always edit the
|
|
26
|
+
default admin credentials.
|
|
27
|
+
"""
|