fractal-server 2.16.5__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/job.py +3 -4
- 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/api/v2/workflow_import.py +4 -3
- 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.5.dist-info → fractal_server-2.17.0a0.dist-info}/METADATA +5 -5
- {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/RECORD +112 -90
- {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/WHEEL +1 -1
- fractal_server/config.py +0 -906
- /fractal_server/{runner → app}/shutdown.py +0 -0
- {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from os import environ
|
|
3
|
+
from os import getenv
|
|
4
|
+
from typing import Literal
|
|
5
|
+
from typing import TypeVar
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
from pydantic import model_validator
|
|
10
|
+
from pydantic import SecretStr
|
|
11
|
+
from pydantic_settings import BaseSettings
|
|
12
|
+
from pydantic_settings import SettingsConfigDict
|
|
13
|
+
|
|
14
|
+
from ._settings_config import SETTINGS_CONFIG_DICT
|
|
15
|
+
from fractal_server.types import AbsolutePathStr
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FractalConfigurationError(ValueError):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
T = TypeVar("T")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OAuthClientConfig(BaseModel):
|
|
26
|
+
"""
|
|
27
|
+
OAuth Client Config Model
|
|
28
|
+
|
|
29
|
+
This model wraps the variables that define a client against an Identity
|
|
30
|
+
Provider. As some providers are supported by the libraries used within the
|
|
31
|
+
server, some attributes are optional.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
CLIENT_NAME:
|
|
35
|
+
The name of the client
|
|
36
|
+
CLIENT_ID:
|
|
37
|
+
ID of client
|
|
38
|
+
CLIENT_SECRET:
|
|
39
|
+
Secret to authorise against the identity provider
|
|
40
|
+
OIDC_CONFIGURATION_ENDPOINT:
|
|
41
|
+
OpenID configuration endpoint,
|
|
42
|
+
allowing to discover the required endpoints automatically
|
|
43
|
+
REDIRECT_URL:
|
|
44
|
+
String to be used as `redirect_url` argument for
|
|
45
|
+
`fastapi_users.get_oauth_router`, and then in
|
|
46
|
+
`httpx_oauth.integrations.fastapi.OAuth2AuthorizeCallback`.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
CLIENT_NAME: str
|
|
50
|
+
CLIENT_ID: str
|
|
51
|
+
CLIENT_SECRET: SecretStr
|
|
52
|
+
OIDC_CONFIGURATION_ENDPOINT: str | None = None
|
|
53
|
+
REDIRECT_URL: str | None = None
|
|
54
|
+
|
|
55
|
+
@model_validator(mode="before")
|
|
56
|
+
@classmethod
|
|
57
|
+
def check_configuration(cls, values):
|
|
58
|
+
if values.get("CLIENT_NAME") not in ["GOOGLE", "GITHUB"]:
|
|
59
|
+
if not values.get("OIDC_CONFIGURATION_ENDPOINT"):
|
|
60
|
+
raise FractalConfigurationError(
|
|
61
|
+
f"Missing OAUTH_{values.get('CLIENT_NAME')}"
|
|
62
|
+
"_OIDC_CONFIGURATION_ENDPOINT"
|
|
63
|
+
)
|
|
64
|
+
return values
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Settings(BaseSettings):
|
|
68
|
+
"""
|
|
69
|
+
Contains all the configuration variables for Fractal Server
|
|
70
|
+
|
|
71
|
+
The attributes of this class are set from the environment.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
model_config = SettingsConfigDict(**SETTINGS_CONFIG_DICT)
|
|
75
|
+
|
|
76
|
+
OAUTH_CLIENTS_CONFIG: list[OAuthClientConfig] = Field(default_factory=list)
|
|
77
|
+
|
|
78
|
+
# JWT TOKEN
|
|
79
|
+
JWT_EXPIRE_SECONDS: int = 180
|
|
80
|
+
"""
|
|
81
|
+
JWT token lifetime, in seconds.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
JWT_SECRET_KEY: SecretStr
|
|
85
|
+
"""
|
|
86
|
+
JWT secret
|
|
87
|
+
|
|
88
|
+
⚠️ **IMPORTANT**: set this variable to a secure string, and do not disclose
|
|
89
|
+
it.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
# COOKIE TOKEN
|
|
93
|
+
COOKIE_EXPIRE_SECONDS: int = 86400
|
|
94
|
+
"""
|
|
95
|
+
Cookie token lifetime, in seconds.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
@model_validator(mode="before")
|
|
99
|
+
@classmethod
|
|
100
|
+
def collect_oauth_clients(cls, values):
|
|
101
|
+
"""
|
|
102
|
+
Automatic collection of OAuth Clients
|
|
103
|
+
|
|
104
|
+
This method collects the environment variables relative to a single
|
|
105
|
+
OAuth client and saves them within the `Settings` object in the form
|
|
106
|
+
of an `OAuthClientConfig` instance.
|
|
107
|
+
|
|
108
|
+
Fractal can support an arbitrary number of OAuth providers, which are
|
|
109
|
+
automatically detected by parsing the environment variable names. In
|
|
110
|
+
particular, to set the provider `FOO`, one must specify the variables
|
|
111
|
+
|
|
112
|
+
OAUTH_FOO_CLIENT_ID
|
|
113
|
+
OAUTH_FOO_CLIENT_SECRET
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
etc (cf. OAuthClientConfig).
|
|
117
|
+
"""
|
|
118
|
+
oauth_env_variable_keys = [
|
|
119
|
+
key for key in environ.keys() if key.startswith("OAUTH_")
|
|
120
|
+
]
|
|
121
|
+
clients_available = {
|
|
122
|
+
var.split("_")[1] for var in oauth_env_variable_keys
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
values["OAUTH_CLIENTS_CONFIG"] = []
|
|
126
|
+
for client in clients_available:
|
|
127
|
+
prefix = f"OAUTH_{client}"
|
|
128
|
+
oauth_client_config = OAuthClientConfig(
|
|
129
|
+
CLIENT_NAME=client,
|
|
130
|
+
CLIENT_ID=getenv(f"{prefix}_CLIENT_ID", None),
|
|
131
|
+
CLIENT_SECRET=getenv(f"{prefix}_CLIENT_SECRET", None),
|
|
132
|
+
OIDC_CONFIGURATION_ENDPOINT=getenv(
|
|
133
|
+
f"{prefix}_OIDC_CONFIGURATION_ENDPOINT", None
|
|
134
|
+
),
|
|
135
|
+
REDIRECT_URL=getenv(f"{prefix}_REDIRECT_URL", None),
|
|
136
|
+
)
|
|
137
|
+
values["OAUTH_CLIENTS_CONFIG"].append(oauth_client_config)
|
|
138
|
+
return values
|
|
139
|
+
|
|
140
|
+
###########################################################################
|
|
141
|
+
# FRACTAL SPECIFIC
|
|
142
|
+
###########################################################################
|
|
143
|
+
|
|
144
|
+
# Note: we do not use ResourceType here to avoid circular imports
|
|
145
|
+
FRACTAL_RUNNER_BACKEND: Literal[
|
|
146
|
+
"local", "slurm_ssh", "slurm_sudo"
|
|
147
|
+
] = "local"
|
|
148
|
+
"""
|
|
149
|
+
Select which runner backend to use.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
FRACTAL_LOGGING_LEVEL: int = logging.INFO
|
|
153
|
+
"""
|
|
154
|
+
Logging-level threshold for logging
|
|
155
|
+
|
|
156
|
+
Only logs of with this level (or higher) will appear in the console logs.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
FRACTAL_API_MAX_JOB_LIST_LENGTH: int = 50
|
|
160
|
+
"""
|
|
161
|
+
Number of ids that can be stored in the `jobsV2` attribute of
|
|
162
|
+
`app.state`.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
FRACTAL_GRACEFUL_SHUTDOWN_TIME: int = 30
|
|
166
|
+
"""
|
|
167
|
+
Waiting time for the shutdown phase of executors
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
FRACTAL_VIEWER_AUTHORIZATION_SCHEME: Literal[
|
|
171
|
+
"viewer-paths", "users-folders", "none"
|
|
172
|
+
] = "none"
|
|
173
|
+
"""
|
|
174
|
+
Defines how the list of allowed viewer paths is built.
|
|
175
|
+
|
|
176
|
+
This variable affects the `GET /auth/current-user/allowed-viewer-paths/`
|
|
177
|
+
response, which is then consumed by
|
|
178
|
+
[fractal-vizarr-viewer](https://github.com/fractal-analytics-platform/fractal-vizarr-viewer).
|
|
179
|
+
|
|
180
|
+
Options:
|
|
181
|
+
|
|
182
|
+
- "viewer-paths": The list of allowed viewer paths will include the user's
|
|
183
|
+
`project_dir` along with any path defined in user groups' `viewer_paths`
|
|
184
|
+
attributes.
|
|
185
|
+
- "users-folders": The list will consist of the user's `project_dir` and a
|
|
186
|
+
user-specific folder. The user folder is constructed by concatenating
|
|
187
|
+
the base folder `FRACTAL_VIEWER_BASE_FOLDER` with the user's
|
|
188
|
+
`slurm_user`.
|
|
189
|
+
- "none": An empty list will be returned, indicating no access to
|
|
190
|
+
viewer paths. Useful when vizarr viewer is not used.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
FRACTAL_VIEWER_BASE_FOLDER: AbsolutePathStr | None = None
|
|
194
|
+
"""
|
|
195
|
+
Base path to Zarr files that will be served by fractal-vizarr-viewer;
|
|
196
|
+
This variable is required and used only when
|
|
197
|
+
FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders".
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def check(self):
|
|
201
|
+
"""
|
|
202
|
+
Make sure that required variables are set
|
|
203
|
+
|
|
204
|
+
This method must be called before the server starts
|
|
205
|
+
"""
|
|
206
|
+
# FRACTAL_VIEWER_BASE_FOLDER is required when
|
|
207
|
+
# FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders"
|
|
208
|
+
# and it must be an absolute path
|
|
209
|
+
if self.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "users-folders":
|
|
210
|
+
viewer_base_folder = self.FRACTAL_VIEWER_BASE_FOLDER
|
|
211
|
+
if viewer_base_folder is None:
|
|
212
|
+
raise FractalConfigurationError(
|
|
213
|
+
"FRACTAL_VIEWER_BASE_FOLDER is required when "
|
|
214
|
+
"FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "
|
|
215
|
+
"users-folders"
|
|
216
|
+
)
|
fractal_server/images/tools.py
CHANGED
|
@@ -16,7 +16,7 @@ def find_image_by_zarr_url(
|
|
|
16
16
|
"""
|
|
17
17
|
Return a copy of the image with a given zarr_url, and its positional index.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Args:
|
|
20
20
|
images: List of images.
|
|
21
21
|
zarr_url: Path that the returned image must have.
|
|
22
22
|
|
|
@@ -40,7 +40,7 @@ def match_filter(
|
|
|
40
40
|
"""
|
|
41
41
|
Find whether an image matches a filter set.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Args:
|
|
44
44
|
image: A single image.
|
|
45
45
|
type_filters:
|
|
46
46
|
attribute_filters:
|
|
@@ -70,7 +70,7 @@ def filter_image_list(
|
|
|
70
70
|
"""
|
|
71
71
|
Compute a sublist with images that match a filter set.
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Args:
|
|
74
74
|
images: A list of images.
|
|
75
75
|
type_filters:
|
|
76
76
|
attribute_filters:
|
fractal_server/logger.py
CHANGED
|
@@ -44,7 +44,7 @@ def get_logger(logger_name: str | None = None) -> logging.Logger:
|
|
|
44
44
|
close_logger(logger)
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
Args:
|
|
48
48
|
logger_name: Name of logger
|
|
49
49
|
Returns:
|
|
50
50
|
Logger with name `logger_name`
|
|
@@ -124,7 +124,7 @@ def close_logger(logger: logging.Logger) -> None:
|
|
|
124
124
|
"""
|
|
125
125
|
Close all handlers associated to a `logging.Logger` object
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
Args:
|
|
128
128
|
logger: The actual logger
|
|
129
129
|
"""
|
|
130
130
|
for handle in logger.handlers:
|
|
@@ -135,7 +135,7 @@ def reset_logger_handlers(logger: logging.Logger) -> None:
|
|
|
135
135
|
"""
|
|
136
136
|
Close and remove all handlers associated to a `logging.Logger` object
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
Args:
|
|
139
139
|
logger: The actual logger
|
|
140
140
|
"""
|
|
141
141
|
close_logger(logger)
|
fractal_server/main.py
CHANGED
|
@@ -1,34 +1,21 @@
|
|
|
1
|
-
# Copyright 2022 (C) Friedrich Miescher Institute for Biomedical Research and
|
|
2
|
-
# University of Zurich
|
|
3
|
-
#
|
|
4
|
-
# Original authors:
|
|
5
|
-
# Jacopo Nespolo <jacopo.nespolo@exact-lab.it>
|
|
6
|
-
# Marco Franzon <marco.franzon@exact-lab.it>
|
|
7
|
-
# Tommaso Comaprin <tommaso.comparin@exact-lab.it>
|
|
8
|
-
#
|
|
9
|
-
# This file is part of Fractal and was originally developed by eXact lab S.r.l.
|
|
10
|
-
# <exact-lab.it> under contract with Liberali Lab from the Friedrich Miescher
|
|
11
|
-
# Institute for Biomedical Research and Pelkmans Lab from the University of
|
|
12
|
-
# Zurich.
|
|
13
|
-
"""
|
|
14
|
-
# Application factory
|
|
15
|
-
|
|
16
|
-
This module sets up the FastAPI application that serves the Fractal Server.
|
|
17
|
-
"""
|
|
18
1
|
import os
|
|
19
2
|
from contextlib import asynccontextmanager
|
|
3
|
+
from itertools import chain
|
|
20
4
|
|
|
21
5
|
from fastapi import FastAPI
|
|
22
6
|
|
|
23
7
|
from .app.routes.aux._runner import _backend_supports_shutdown
|
|
8
|
+
from .app.shutdown import cleanup_after_shutdown
|
|
9
|
+
from .config import get_db_settings
|
|
10
|
+
from .config import get_email_settings
|
|
24
11
|
from .config import get_settings
|
|
25
12
|
from .logger import config_uvicorn_loggers
|
|
26
13
|
from .logger import get_logger
|
|
27
14
|
from .logger import reset_logger_handlers
|
|
28
15
|
from .logger import set_logger
|
|
29
|
-
from .runner.shutdown import cleanup_after_shutdown
|
|
30
16
|
from .syringe import Inject
|
|
31
17
|
from fractal_server import __VERSION__
|
|
18
|
+
from fractal_server.app.schemas.v2 import ResourceType
|
|
32
19
|
|
|
33
20
|
|
|
34
21
|
def collect_routers(app: FastAPI) -> None:
|
|
@@ -64,10 +51,16 @@ def check_settings() -> None:
|
|
|
64
51
|
"""
|
|
65
52
|
settings = Inject(get_settings)
|
|
66
53
|
settings.check()
|
|
54
|
+
db_settings = Inject(get_db_settings)
|
|
55
|
+
email_settings = Inject(get_email_settings)
|
|
67
56
|
|
|
68
57
|
logger = set_logger("fractal_server_settings")
|
|
69
58
|
logger.debug("Fractal Settings:")
|
|
70
|
-
for key, value in
|
|
59
|
+
for key, value in chain(
|
|
60
|
+
db_settings.model_dump().items(),
|
|
61
|
+
settings.model_dump().items(),
|
|
62
|
+
email_settings.model_dump().items(),
|
|
63
|
+
):
|
|
71
64
|
if any(s in key.upper() for s in ["PASSWORD", "SECRET", "KEY"]):
|
|
72
65
|
value = "*****"
|
|
73
66
|
logger.debug(f" {key}: {value}")
|
|
@@ -82,7 +75,7 @@ async def lifespan(app: FastAPI):
|
|
|
82
75
|
check_settings()
|
|
83
76
|
settings = Inject(get_settings)
|
|
84
77
|
|
|
85
|
-
if settings.FRACTAL_RUNNER_BACKEND ==
|
|
78
|
+
if settings.FRACTAL_RUNNER_BACKEND == ResourceType.SLURM_SSH:
|
|
86
79
|
from fractal_server.ssh._fabric import FractalSSHList
|
|
87
80
|
|
|
88
81
|
app.state.fractal_ssh_list = FractalSSHList()
|
|
@@ -103,7 +96,7 @@ async def lifespan(app: FastAPI):
|
|
|
103
96
|
logger = get_logger("fractal_server.lifespan")
|
|
104
97
|
logger.info("[teardown] START")
|
|
105
98
|
|
|
106
|
-
if settings.FRACTAL_RUNNER_BACKEND ==
|
|
99
|
+
if settings.FRACTAL_RUNNER_BACKEND == ResourceType.SLURM_SSH:
|
|
107
100
|
logger.info(
|
|
108
101
|
"[teardown] Close FractalSSH connections "
|
|
109
102
|
f"(current size: {app.state.fractal_ssh_list.size})."
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""drop useroauth.username
|
|
2
|
+
|
|
3
|
+
Revision ID: 90f6508c6379
|
|
4
|
+
Revises: a80ac5a352bf
|
|
5
|
+
Create Date: 2025-10-15 16:09:40.922407
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import sqlalchemy as sa
|
|
9
|
+
from alembic import op
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# revision identifiers, used by Alembic.
|
|
13
|
+
revision = "90f6508c6379"
|
|
14
|
+
down_revision = "a80ac5a352bf"
|
|
15
|
+
branch_labels = None
|
|
16
|
+
depends_on = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
21
|
+
with op.batch_alter_table("user_oauth", schema=None) as batch_op:
|
|
22
|
+
batch_op.drop_column("username")
|
|
23
|
+
|
|
24
|
+
# ### end Alembic commands ###
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def downgrade() -> None:
|
|
28
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
29
|
+
with op.batch_alter_table("user_oauth", schema=None) as batch_op:
|
|
30
|
+
batch_op.add_column(
|
|
31
|
+
sa.Column(
|
|
32
|
+
"username", sa.VARCHAR(), autoincrement=False, nullable=True
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""resource-profile
|
|
2
|
+
|
|
3
|
+
Revision ID: a80ac5a352bf
|
|
4
|
+
Revises: 981d588fe248
|
|
5
|
+
Create Date: 2025-10-15 15:53:34.823398
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import sqlalchemy as sa
|
|
9
|
+
import sqlmodel
|
|
10
|
+
from alembic import op
|
|
11
|
+
from sqlalchemy.dialects import postgresql
|
|
12
|
+
|
|
13
|
+
# revision identifiers, used by Alembic.
|
|
14
|
+
revision = "a80ac5a352bf"
|
|
15
|
+
down_revision = "981d588fe248"
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
22
|
+
op.create_table(
|
|
23
|
+
"resource",
|
|
24
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
25
|
+
sa.Column("type", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
26
|
+
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
27
|
+
sa.Column(
|
|
28
|
+
"timestamp_created", sa.DateTime(timezone=True), nullable=False
|
|
29
|
+
),
|
|
30
|
+
sa.Column("host", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
|
31
|
+
sa.Column(
|
|
32
|
+
"jobs_local_dir",
|
|
33
|
+
sqlmodel.sql.sqltypes.AutoString(),
|
|
34
|
+
nullable=False,
|
|
35
|
+
),
|
|
36
|
+
sa.Column(
|
|
37
|
+
"jobs_runner_config",
|
|
38
|
+
postgresql.JSONB(astext_type=sa.Text()),
|
|
39
|
+
server_default="{}",
|
|
40
|
+
nullable=False,
|
|
41
|
+
),
|
|
42
|
+
sa.Column(
|
|
43
|
+
"jobs_slurm_python_worker",
|
|
44
|
+
sqlmodel.sql.sqltypes.AutoString(),
|
|
45
|
+
nullable=True,
|
|
46
|
+
),
|
|
47
|
+
sa.Column("jobs_poll_interval", sa.Integer(), nullable=False),
|
|
48
|
+
sa.Column(
|
|
49
|
+
"tasks_local_dir",
|
|
50
|
+
sqlmodel.sql.sqltypes.AutoString(),
|
|
51
|
+
nullable=False,
|
|
52
|
+
),
|
|
53
|
+
sa.Column(
|
|
54
|
+
"tasks_python_config",
|
|
55
|
+
postgresql.JSONB(astext_type=sa.Text()),
|
|
56
|
+
server_default="{}",
|
|
57
|
+
nullable=False,
|
|
58
|
+
),
|
|
59
|
+
sa.Column(
|
|
60
|
+
"tasks_pixi_config",
|
|
61
|
+
postgresql.JSONB(astext_type=sa.Text()),
|
|
62
|
+
server_default="{}",
|
|
63
|
+
nullable=False,
|
|
64
|
+
),
|
|
65
|
+
sa.CheckConstraint(
|
|
66
|
+
"(type = 'local') OR (jobs_slurm_python_worker IS NOT NULL)",
|
|
67
|
+
name=op.f(
|
|
68
|
+
"ck_resource_`ck_resource_jobs_slurm_python_worker_set`"
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
sa.CheckConstraint(
|
|
72
|
+
"type IN ('local', 'slurm_sudo', 'slurm_ssh')",
|
|
73
|
+
name=op.f("ck_resource_`ck_resource_correct_type`"),
|
|
74
|
+
),
|
|
75
|
+
sa.PrimaryKeyConstraint("id", name=op.f("pk_resource")),
|
|
76
|
+
sa.UniqueConstraint("name", name=op.f("uq_resource_name")),
|
|
77
|
+
)
|
|
78
|
+
op.create_table(
|
|
79
|
+
"profile",
|
|
80
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
81
|
+
sa.Column("resource_id", sa.Integer(), nullable=False),
|
|
82
|
+
sa.Column(
|
|
83
|
+
"resource_type", sqlmodel.sql.sqltypes.AutoString(), nullable=False
|
|
84
|
+
),
|
|
85
|
+
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
|
86
|
+
sa.Column(
|
|
87
|
+
"username", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
|
88
|
+
),
|
|
89
|
+
sa.Column(
|
|
90
|
+
"ssh_key_path", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
|
91
|
+
),
|
|
92
|
+
sa.Column(
|
|
93
|
+
"jobs_remote_dir",
|
|
94
|
+
sqlmodel.sql.sqltypes.AutoString(),
|
|
95
|
+
nullable=True,
|
|
96
|
+
),
|
|
97
|
+
sa.Column(
|
|
98
|
+
"tasks_remote_dir",
|
|
99
|
+
sqlmodel.sql.sqltypes.AutoString(),
|
|
100
|
+
nullable=True,
|
|
101
|
+
),
|
|
102
|
+
sa.ForeignKeyConstraint(
|
|
103
|
+
["resource_id"],
|
|
104
|
+
["resource.id"],
|
|
105
|
+
name=op.f("fk_profile_resource_id_resource"),
|
|
106
|
+
ondelete="CASCADE",
|
|
107
|
+
),
|
|
108
|
+
sa.PrimaryKeyConstraint("id", name=op.f("pk_profile")),
|
|
109
|
+
sa.UniqueConstraint("name", name=op.f("uq_profile_name")),
|
|
110
|
+
)
|
|
111
|
+
with op.batch_alter_table("projectv2", schema=None) as batch_op:
|
|
112
|
+
batch_op.add_column(
|
|
113
|
+
sa.Column("resource_id", sa.Integer(), nullable=True)
|
|
114
|
+
)
|
|
115
|
+
batch_op.create_foreign_key(
|
|
116
|
+
batch_op.f("fk_projectv2_resource_id_resource"),
|
|
117
|
+
"resource",
|
|
118
|
+
["resource_id"],
|
|
119
|
+
["id"],
|
|
120
|
+
ondelete="SET NULL",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
with op.batch_alter_table("taskgroupv2", schema=None) as batch_op:
|
|
124
|
+
batch_op.add_column(
|
|
125
|
+
sa.Column("resource_id", sa.Integer(), nullable=True)
|
|
126
|
+
)
|
|
127
|
+
batch_op.create_foreign_key(
|
|
128
|
+
batch_op.f("fk_taskgroupv2_resource_id_resource"),
|
|
129
|
+
"resource",
|
|
130
|
+
["resource_id"],
|
|
131
|
+
["id"],
|
|
132
|
+
ondelete="SET NULL",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
with op.batch_alter_table("user_oauth", schema=None) as batch_op:
|
|
136
|
+
batch_op.add_column(
|
|
137
|
+
sa.Column("profile_id", sa.Integer(), nullable=True)
|
|
138
|
+
)
|
|
139
|
+
batch_op.create_foreign_key(
|
|
140
|
+
batch_op.f("fk_user_oauth_profile_id_profile"),
|
|
141
|
+
"profile",
|
|
142
|
+
["profile_id"],
|
|
143
|
+
["id"],
|
|
144
|
+
ondelete="SET NULL",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
with op.batch_alter_table("user_settings", schema=None) as batch_op:
|
|
148
|
+
batch_op.drop_column("ssh_jobs_dir")
|
|
149
|
+
batch_op.drop_column("ssh_tasks_dir")
|
|
150
|
+
|
|
151
|
+
# ### end Alembic commands ###
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def downgrade() -> None:
|
|
155
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
156
|
+
with op.batch_alter_table("user_settings", schema=None) as batch_op:
|
|
157
|
+
batch_op.add_column(
|
|
158
|
+
sa.Column(
|
|
159
|
+
"ssh_tasks_dir",
|
|
160
|
+
sa.VARCHAR(),
|
|
161
|
+
autoincrement=False,
|
|
162
|
+
nullable=True,
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
batch_op.add_column(
|
|
166
|
+
sa.Column(
|
|
167
|
+
"ssh_jobs_dir",
|
|
168
|
+
sa.VARCHAR(),
|
|
169
|
+
autoincrement=False,
|
|
170
|
+
nullable=True,
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
with op.batch_alter_table("user_oauth", schema=None) as batch_op:
|
|
175
|
+
batch_op.drop_constraint(
|
|
176
|
+
batch_op.f("fk_user_oauth_profile_id_profile"), type_="foreignkey"
|
|
177
|
+
)
|
|
178
|
+
batch_op.drop_column("profile_id")
|
|
179
|
+
|
|
180
|
+
with op.batch_alter_table("taskgroupv2", schema=None) as batch_op:
|
|
181
|
+
batch_op.drop_constraint(
|
|
182
|
+
batch_op.f("fk_taskgroupv2_resource_id_resource"),
|
|
183
|
+
type_="foreignkey",
|
|
184
|
+
)
|
|
185
|
+
batch_op.drop_column("resource_id")
|
|
186
|
+
|
|
187
|
+
with op.batch_alter_table("projectv2", schema=None) as batch_op:
|
|
188
|
+
batch_op.drop_constraint(
|
|
189
|
+
batch_op.f("fk_projectv2_resource_id_resource"), type_="foreignkey"
|
|
190
|
+
)
|
|
191
|
+
batch_op.drop_column("resource_id")
|
|
192
|
+
|
|
193
|
+
op.drop_table("profile")
|
|
194
|
+
op.drop_table("resource")
|
|
195
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from pydantic import ConfigDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class JobRunnerConfigLocal(BaseModel):
|
|
6
|
+
"""
|
|
7
|
+
Specifications of the local-backend configuration
|
|
8
|
+
|
|
9
|
+
Attributes:
|
|
10
|
+
parallel_tasks_per_job:
|
|
11
|
+
Maximum number of tasks to be run in parallel as part of a call to
|
|
12
|
+
`FractalThreadPoolExecutor.map`; if `None`, then all tasks will
|
|
13
|
+
start at the same time.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(extra="forbid")
|
|
17
|
+
parallel_tasks_per_job: int | None = None
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def batch_size(self) -> int:
|
|
21
|
+
return self.parallel_tasks_per_job or 1
|