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.
Files changed (111) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +129 -22
  3. fractal_server/app/db/__init__.py +9 -11
  4. fractal_server/app/models/security.py +7 -3
  5. fractal_server/app/models/user_settings.py +0 -4
  6. fractal_server/app/models/v2/__init__.py +4 -0
  7. fractal_server/app/models/v2/profile.py +16 -0
  8. fractal_server/app/models/v2/project.py +3 -0
  9. fractal_server/app/models/v2/resource.py +130 -0
  10. fractal_server/app/models/v2/task_group.py +3 -0
  11. fractal_server/app/routes/admin/v2/__init__.py +4 -0
  12. fractal_server/app/routes/admin/v2/_aux_functions.py +55 -0
  13. fractal_server/app/routes/admin/v2/profile.py +86 -0
  14. fractal_server/app/routes/admin/v2/resource.py +229 -0
  15. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +48 -82
  16. fractal_server/app/routes/api/__init__.py +26 -7
  17. fractal_server/app/routes/api/v2/_aux_functions.py +27 -1
  18. fractal_server/app/routes/api/v2/_aux_functions_history.py +2 -2
  19. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
  20. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +7 -7
  21. fractal_server/app/routes/api/v2/project.py +5 -1
  22. fractal_server/app/routes/api/v2/submit.py +32 -24
  23. fractal_server/app/routes/api/v2/task.py +5 -0
  24. fractal_server/app/routes/api/v2/task_collection.py +36 -47
  25. fractal_server/app/routes/api/v2/task_collection_custom.py +11 -5
  26. fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -40
  27. fractal_server/app/routes/api/v2/task_group_lifecycle.py +39 -82
  28. fractal_server/app/routes/auth/_aux_auth.py +3 -3
  29. fractal_server/app/routes/auth/current_user.py +45 -7
  30. fractal_server/app/routes/auth/oauth.py +1 -1
  31. fractal_server/app/routes/auth/users.py +9 -0
  32. fractal_server/app/routes/aux/_runner.py +2 -1
  33. fractal_server/app/routes/aux/validate_user_profile.py +62 -0
  34. fractal_server/app/routes/aux/validate_user_settings.py +12 -9
  35. fractal_server/app/schemas/user.py +20 -13
  36. fractal_server/app/schemas/user_settings.py +0 -4
  37. fractal_server/app/schemas/v2/__init__.py +11 -0
  38. fractal_server/app/schemas/v2/profile.py +72 -0
  39. fractal_server/app/schemas/v2/resource.py +117 -0
  40. fractal_server/app/security/__init__.py +6 -13
  41. fractal_server/app/security/signup_email.py +2 -2
  42. fractal_server/app/user_settings.py +2 -12
  43. fractal_server/config/__init__.py +23 -0
  44. fractal_server/config/_database.py +58 -0
  45. fractal_server/config/_email.py +170 -0
  46. fractal_server/config/_init_data.py +27 -0
  47. fractal_server/config/_main.py +216 -0
  48. fractal_server/config/_settings_config.py +7 -0
  49. fractal_server/images/tools.py +3 -3
  50. fractal_server/logger.py +3 -3
  51. fractal_server/main.py +14 -21
  52. fractal_server/migrations/versions/90f6508c6379_drop_useroauth_username.py +36 -0
  53. fractal_server/migrations/versions/a80ac5a352bf_resource_profile.py +195 -0
  54. fractal_server/runner/config/__init__.py +2 -0
  55. fractal_server/runner/config/_local.py +21 -0
  56. fractal_server/runner/config/_slurm.py +128 -0
  57. fractal_server/runner/config/slurm_mem_to_MB.py +63 -0
  58. fractal_server/runner/exceptions.py +4 -0
  59. fractal_server/runner/executors/base_runner.py +17 -7
  60. fractal_server/runner/executors/local/get_local_config.py +21 -86
  61. fractal_server/runner/executors/local/runner.py +48 -5
  62. fractal_server/runner/executors/slurm_common/_batching.py +2 -2
  63. fractal_server/runner/executors/slurm_common/base_slurm_runner.py +59 -25
  64. fractal_server/runner/executors/slurm_common/get_slurm_config.py +38 -54
  65. fractal_server/runner/executors/slurm_common/remote.py +1 -1
  66. fractal_server/runner/executors/slurm_common/{_slurm_config.py → slurm_config.py} +3 -254
  67. fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -1
  68. fractal_server/runner/executors/slurm_ssh/runner.py +12 -14
  69. fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -2
  70. fractal_server/runner/executors/slurm_sudo/runner.py +12 -12
  71. fractal_server/runner/v2/_local.py +36 -21
  72. fractal_server/runner/v2/_slurm_ssh.py +40 -4
  73. fractal_server/runner/v2/_slurm_sudo.py +41 -11
  74. fractal_server/runner/v2/db_tools.py +1 -1
  75. fractal_server/runner/v2/runner.py +3 -11
  76. fractal_server/runner/v2/runner_functions.py +42 -28
  77. fractal_server/runner/v2/submit_workflow.py +87 -108
  78. fractal_server/runner/versions.py +8 -3
  79. fractal_server/ssh/_fabric.py +6 -6
  80. fractal_server/tasks/config/__init__.py +3 -0
  81. fractal_server/tasks/config/_pixi.py +127 -0
  82. fractal_server/tasks/config/_python.py +51 -0
  83. fractal_server/tasks/v2/local/_utils.py +7 -7
  84. fractal_server/tasks/v2/local/collect.py +13 -5
  85. fractal_server/tasks/v2/local/collect_pixi.py +26 -10
  86. fractal_server/tasks/v2/local/deactivate.py +7 -1
  87. fractal_server/tasks/v2/local/deactivate_pixi.py +5 -1
  88. fractal_server/tasks/v2/local/delete.py +4 -0
  89. fractal_server/tasks/v2/local/reactivate.py +13 -5
  90. fractal_server/tasks/v2/local/reactivate_pixi.py +27 -9
  91. fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +11 -10
  92. fractal_server/tasks/v2/ssh/_utils.py +6 -7
  93. fractal_server/tasks/v2/ssh/collect.py +19 -12
  94. fractal_server/tasks/v2/ssh/collect_pixi.py +34 -16
  95. fractal_server/tasks/v2/ssh/deactivate.py +12 -8
  96. fractal_server/tasks/v2/ssh/deactivate_pixi.py +14 -10
  97. fractal_server/tasks/v2/ssh/delete.py +12 -9
  98. fractal_server/tasks/v2/ssh/reactivate.py +18 -12
  99. fractal_server/tasks/v2/ssh/reactivate_pixi.py +36 -17
  100. fractal_server/tasks/v2/templates/4_pip_show.sh +4 -6
  101. fractal_server/tasks/v2/utils_database.py +2 -2
  102. fractal_server/tasks/v2/utils_python_interpreter.py +8 -16
  103. fractal_server/tasks/v2/utils_templates.py +7 -10
  104. fractal_server/utils.py +1 -1
  105. {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/METADATA +1 -1
  106. {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/RECORD +110 -88
  107. fractal_server/config.py +0 -906
  108. /fractal_server/{runner → app}/shutdown.py +0 -0
  109. {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/WHEEL +0 -0
  110. {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/entry_points.txt +0 -0
  111. {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 get_settings
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
- settings = Inject(get_settings)
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"{settings.email_settings.recipients}."
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=settings.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
- Arguments:
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 MailSettings
7
+ from fractal_server.config import PublicEmailSettings
8
8
 
9
9
 
10
- def mail_new_oauth_signup(msg: str, email_settings: MailSettings):
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
- ssh_host: SSH-reachable host where a SLURM client is available.
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
- ssh_host: str
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
+ """