fractal-server 2.17.0a5__py3-none-any.whl → 2.17.0a7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fractal_server/__init__.py +1 -1
- fractal_server/app/models/security.py +7 -1
- fractal_server/app/models/user_settings.py +4 -0
- fractal_server/app/models/v2/profile.py +1 -1
- fractal_server/app/models/v2/project.py +3 -1
- fractal_server/app/models/v2/resource.py +2 -2
- fractal_server/app/models/v2/task_group.py +1 -1
- fractal_server/app/routes/admin/v2/profile.py +14 -0
- fractal_server/app/routes/admin/v2/resource.py +9 -52
- fractal_server/app/routes/admin/v2/task.py +9 -0
- fractal_server/app/routes/admin/v2/task_group.py +11 -7
- fractal_server/app/routes/api/__init__.py +9 -0
- fractal_server/app/routes/api/v2/_aux_functions.py +6 -16
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +35 -7
- fractal_server/app/routes/api/v2/project.py +8 -4
- fractal_server/app/routes/api/v2/submit.py +4 -3
- fractal_server/app/routes/api/v2/task.py +18 -5
- fractal_server/app/routes/api/v2/task_collection.py +3 -5
- fractal_server/app/routes/api/v2/task_collection_custom.py +2 -5
- fractal_server/app/routes/api/v2/task_collection_pixi.py +2 -5
- fractal_server/app/routes/auth/current_user.py +9 -14
- fractal_server/app/routes/auth/oauth.py +16 -6
- fractal_server/app/routes/auth/users.py +1 -2
- fractal_server/app/schemas/user.py +3 -3
- fractal_server/app/schemas/v2/__init__.py +1 -0
- fractal_server/app/schemas/v2/task_group.py +4 -0
- fractal_server/config/__init__.py +6 -1
- fractal_server/config/_data.py +68 -0
- fractal_server/config/_main.py +1 -65
- fractal_server/config/_oauth.py +2 -2
- fractal_server/main.py +3 -2
- fractal_server/migrations/naming_convention.py +1 -1
- fractal_server/migrations/versions/{a80ac5a352bf_resource_profile.py → 83bc2ad3ffcc_2_17_0.py} +31 -31
- {fractal_server-2.17.0a5.dist-info → fractal_server-2.17.0a7.dist-info}/METADATA +4 -5
- {fractal_server-2.17.0a5.dist-info → fractal_server-2.17.0a7.dist-info}/RECORD +38 -40
- fractal_server/data_migrations/2_14_10.py +0 -48
- fractal_server/migrations/versions/90f6508c6379_drop_useroauth_username.py +0 -36
- fractal_server/migrations/versions/f65ee53991e3_user_settings_related.py +0 -67
- {fractal_server-2.17.0a5.dist-info → fractal_server-2.17.0a7.dist-info}/WHEEL +0 -0
- {fractal_server-2.17.0a5.dist-info → fractal_server-2.17.0a7.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.17.0a5.dist-info → fractal_server-2.17.0a7.dist-info}/licenses/LICENSE +0 -0
|
@@ -10,7 +10,6 @@ from fastapi import Response
|
|
|
10
10
|
from fastapi import status
|
|
11
11
|
from fastapi import UploadFile
|
|
12
12
|
|
|
13
|
-
from ._aux_functions import _get_resource_and_profile_ids
|
|
14
13
|
from fractal_server.app.db import AsyncSession
|
|
15
14
|
from fractal_server.app.db import get_async_db
|
|
16
15
|
from fractal_server.app.models import UserOAuth
|
|
@@ -90,6 +89,7 @@ async def collect_task_pixi(
|
|
|
90
89
|
) -> TaskGroupActivityV2Read:
|
|
91
90
|
# Get validated resource and profile
|
|
92
91
|
resource, profile = await validate_user_profile(user=user, db=db)
|
|
92
|
+
resource_id = resource.id
|
|
93
93
|
|
|
94
94
|
# Check if Pixi is available
|
|
95
95
|
if not resource.tasks_pixi_config:
|
|
@@ -133,10 +133,6 @@ async def collect_task_pixi(
|
|
|
133
133
|
Path(base_tasks_path) / str(user.id) / pkg_name / version
|
|
134
134
|
).as_posix()
|
|
135
135
|
|
|
136
|
-
resource_id, _ = await _get_resource_and_profile_ids(
|
|
137
|
-
user_id=user.id, db=db
|
|
138
|
-
)
|
|
139
|
-
|
|
140
136
|
task_group_attrs = dict(
|
|
141
137
|
user_id=user.id,
|
|
142
138
|
user_group_id=user_group_id,
|
|
@@ -152,6 +148,7 @@ async def collect_task_pixi(
|
|
|
152
148
|
user_id=user.id,
|
|
153
149
|
pkg_name=task_group_attrs["pkg_name"],
|
|
154
150
|
version=task_group_attrs["version"],
|
|
151
|
+
user_resource_id=resource_id,
|
|
155
152
|
db=db,
|
|
156
153
|
)
|
|
157
154
|
await _verify_non_duplication_group_constraint(
|
|
@@ -5,7 +5,6 @@ import os
|
|
|
5
5
|
|
|
6
6
|
from fastapi import APIRouter
|
|
7
7
|
from fastapi import Depends
|
|
8
|
-
from fastapi_users import schemas
|
|
9
8
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
9
|
from sqlmodel import select
|
|
11
10
|
|
|
@@ -25,8 +24,8 @@ from fractal_server.app.schemas.user import UserUpdate
|
|
|
25
24
|
from fractal_server.app.schemas.user import UserUpdateStrict
|
|
26
25
|
from fractal_server.app.security import get_user_manager
|
|
27
26
|
from fractal_server.app.security import UserManager
|
|
28
|
-
from fractal_server.config import
|
|
29
|
-
from fractal_server.config import
|
|
27
|
+
from fractal_server.config import DataAuthScheme
|
|
28
|
+
from fractal_server.config import get_data_settings
|
|
30
29
|
from fractal_server.syringe import Inject
|
|
31
30
|
|
|
32
31
|
router_current_user = APIRouter()
|
|
@@ -66,7 +65,7 @@ async def patch_current_user(
|
|
|
66
65
|
# their own password
|
|
67
66
|
|
|
68
67
|
user = await user_manager.update(update, current_user, safe=True)
|
|
69
|
-
validated_user =
|
|
68
|
+
validated_user = UserOAuth.model_validate(user.model_dump())
|
|
70
69
|
|
|
71
70
|
patched_user = await db.get(
|
|
72
71
|
UserOAuth, validated_user.id, populate_existing=True
|
|
@@ -117,14 +116,14 @@ async def get_current_user_allowed_viewer_paths(
|
|
|
117
116
|
) -> list[str]:
|
|
118
117
|
"""
|
|
119
118
|
Returns the allowed viewer paths for current user, according to the
|
|
120
|
-
selected
|
|
119
|
+
selected FRACTAL_DATA_AUTH_SCHEME
|
|
121
120
|
"""
|
|
122
121
|
|
|
123
|
-
|
|
122
|
+
data_settings = Inject(get_data_settings)
|
|
124
123
|
|
|
125
124
|
authorized_paths = []
|
|
126
125
|
|
|
127
|
-
if
|
|
126
|
+
if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.NONE:
|
|
128
127
|
return authorized_paths
|
|
129
128
|
|
|
130
129
|
# Append `project_dir` to the list of authorized paths
|
|
@@ -133,20 +132,16 @@ async def get_current_user_allowed_viewer_paths(
|
|
|
133
132
|
# If auth scheme is "users-folders" and `slurm_user` is set,
|
|
134
133
|
# build and append the user folder
|
|
135
134
|
if (
|
|
136
|
-
|
|
137
|
-
== ViewerAuthScheme.USERS_FOLDERS
|
|
135
|
+
data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.USERS_FOLDERS
|
|
138
136
|
and current_user.profile_id is not None
|
|
139
137
|
):
|
|
140
138
|
profile = await db.get(Profile, current_user.profile_id)
|
|
141
139
|
if profile is not None and profile.username is not None:
|
|
142
|
-
base_folder =
|
|
140
|
+
base_folder = data_settings.FRACTAL_DATA_BASE_FOLDER
|
|
143
141
|
user_folder = os.path.join(base_folder, profile.username)
|
|
144
142
|
authorized_paths.append(user_folder)
|
|
145
143
|
|
|
146
|
-
if
|
|
147
|
-
settings.FRACTAL_VIEWER_AUTHORIZATION_SCHEME
|
|
148
|
-
== ViewerAuthScheme.VIEWER_PATHS
|
|
149
|
-
):
|
|
144
|
+
if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.VIEWER_PATHS:
|
|
150
145
|
# Returns the union of `viewer_paths` for all user's groups
|
|
151
146
|
cmd = (
|
|
152
147
|
select(UserGroup.viewer_paths)
|
|
@@ -2,6 +2,7 @@ from fastapi import APIRouter
|
|
|
2
2
|
from httpx_oauth.clients.github import GitHubOAuth2
|
|
3
3
|
from httpx_oauth.clients.google import GoogleOAuth2
|
|
4
4
|
from httpx_oauth.clients.openid import OpenID
|
|
5
|
+
from httpx_oauth.clients.openid import OpenIDConfigurationError
|
|
5
6
|
|
|
6
7
|
from . import cookie_backend
|
|
7
8
|
from . import fastapi_users
|
|
@@ -26,11 +27,21 @@ def _create_client_google(cfg: OAuthSettings) -> GoogleOAuth2:
|
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def _create_client_oidc(cfg: OAuthSettings) -> OpenID:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
try:
|
|
31
|
+
open_id = OpenID(
|
|
32
|
+
client_id=cfg.OAUTH_CLIENT_ID.get_secret_value(),
|
|
33
|
+
client_secret=cfg.OAUTH_CLIENT_SECRET.get_secret_value(),
|
|
34
|
+
openid_configuration_endpoint=cfg.OAUTH_OIDC_CONFIG_ENDPOINT.get_secret_value(), # noqa
|
|
35
|
+
)
|
|
36
|
+
except OpenIDConfigurationError as e:
|
|
37
|
+
OAUTH_OIDC_CONFIG_ENDPOINT = (
|
|
38
|
+
cfg.OAUTH_OIDC_CONFIG_ENDPOINT.get_secret_value()
|
|
39
|
+
)
|
|
40
|
+
raise RuntimeError(
|
|
41
|
+
f"Cannot initialize OpenID client. Original error: '{e}'. "
|
|
42
|
+
f"Hint: is {OAUTH_OIDC_CONFIG_ENDPOINT=} reachable?"
|
|
43
|
+
)
|
|
44
|
+
return open_id
|
|
34
45
|
|
|
35
46
|
|
|
36
47
|
def get_oauth_router() -> APIRouter | None:
|
|
@@ -44,7 +55,6 @@ def get_oauth_router() -> APIRouter | None:
|
|
|
44
55
|
return None
|
|
45
56
|
|
|
46
57
|
client_name = oauth_settings.OAUTH_CLIENT_NAME
|
|
47
|
-
|
|
48
58
|
if client_name == "google":
|
|
49
59
|
client = _create_client_google(oauth_settings)
|
|
50
60
|
elif client_name == "github":
|
|
@@ -6,7 +6,6 @@ from fastapi import Depends
|
|
|
6
6
|
from fastapi import HTTPException
|
|
7
7
|
from fastapi import status
|
|
8
8
|
from fastapi_users import exceptions
|
|
9
|
-
from fastapi_users import schemas
|
|
10
9
|
from fastapi_users.router.common import ErrorCode
|
|
11
10
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
11
|
from sqlmodel import func
|
|
@@ -80,7 +79,7 @@ async def patch_user(
|
|
|
80
79
|
safe=False,
|
|
81
80
|
request=None,
|
|
82
81
|
)
|
|
83
|
-
validated_user =
|
|
82
|
+
validated_user = UserOAuth.model_validate(user.model_dump())
|
|
84
83
|
patched_user = await db.get(
|
|
85
84
|
UserOAuth, validated_user.id, populate_existing=True
|
|
86
85
|
)
|
|
@@ -76,8 +76,8 @@ class UserUpdate(schemas.BaseUserUpdate):
|
|
|
76
76
|
profile_id: int | None = None
|
|
77
77
|
project_dir: Annotated[
|
|
78
78
|
AbsolutePathStr, AfterValidator(_validate_cmd)
|
|
79
|
-
]
|
|
80
|
-
slurm_accounts: ListUniqueNonEmptyString
|
|
79
|
+
] = None
|
|
80
|
+
slurm_accounts: ListUniqueNonEmptyString = None
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
class UserUpdateStrict(BaseModel):
|
|
@@ -89,7 +89,7 @@ class UserUpdateStrict(BaseModel):
|
|
|
89
89
|
"""
|
|
90
90
|
|
|
91
91
|
model_config = ConfigDict(extra="forbid")
|
|
92
|
-
slurm_accounts: ListUniqueNonEmptyString
|
|
92
|
+
slurm_accounts: ListUniqueNonEmptyString = None
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
class UserCreate(schemas.BaseUserCreate):
|
|
@@ -52,6 +52,7 @@ from .task_group import TaskGroupActivityStatusV2 # noqa F401
|
|
|
52
52
|
from .task_group import TaskGroupActivityV2Read # noqa F401
|
|
53
53
|
from .task_group import TaskGroupCreateV2 # noqa F401
|
|
54
54
|
from .task_group import TaskGroupCreateV2Strict # noqa F401
|
|
55
|
+
from .task_group import TaskGroupReadSuperuser # noqa F401
|
|
55
56
|
from .task_group import TaskGroupReadV2 # noqa F401
|
|
56
57
|
from .task_group import TaskGroupUpdateV2 # noqa F401
|
|
57
58
|
from .task_group import TaskGroupV2OriginEnum # noqa F401
|
|
@@ -96,6 +96,10 @@ class TaskGroupReadV2(BaseModel):
|
|
|
96
96
|
return v.isoformat()
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
class TaskGroupReadSuperuser(TaskGroupReadV2):
|
|
100
|
+
resource_id: int
|
|
101
|
+
|
|
102
|
+
|
|
99
103
|
class TaskGroupUpdateV2(BaseModel):
|
|
100
104
|
model_config = ConfigDict(extra="forbid")
|
|
101
105
|
user_group_id: int | None = None
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from ._data import DataAuthScheme # noqa F401
|
|
2
|
+
from ._data import DataSettings
|
|
1
3
|
from ._database import DatabaseSettings
|
|
2
4
|
from ._email import EmailSettings
|
|
3
5
|
from ._email import PublicEmailSettings # noqa F401
|
|
4
6
|
from ._main import Settings
|
|
5
|
-
from ._main import ViewerAuthScheme # noqa F401
|
|
6
7
|
from ._oauth import OAuthSettings
|
|
7
8
|
|
|
8
9
|
|
|
@@ -20,3 +21,7 @@ def get_email_settings(email_settings=EmailSettings()) -> EmailSettings:
|
|
|
20
21
|
|
|
21
22
|
def get_oauth_settings(oauth_settings=OAuthSettings()) -> OAuthSettings:
|
|
22
23
|
return oauth_settings
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_data_settings(data_settings=DataSettings()) -> DataSettings:
|
|
27
|
+
return data_settings
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from typing import Self
|
|
3
|
+
|
|
4
|
+
from pydantic import model_validator
|
|
5
|
+
from pydantic_settings import BaseSettings
|
|
6
|
+
from pydantic_settings import SettingsConfigDict
|
|
7
|
+
|
|
8
|
+
from ._settings_config import SETTINGS_CONFIG_DICT
|
|
9
|
+
from fractal_server.types import AbsolutePathStr
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DataAuthScheme(StrEnum):
|
|
13
|
+
VIEWER_PATHS = "viewer-paths"
|
|
14
|
+
USERS_FOLDERS = "users-folders"
|
|
15
|
+
NONE = "none"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DataSettings(BaseSettings):
|
|
19
|
+
"""
|
|
20
|
+
Settings for the `fractal-data` integration.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
model_config = SettingsConfigDict(**SETTINGS_CONFIG_DICT)
|
|
24
|
+
|
|
25
|
+
FRACTAL_DATA_AUTH_SCHEME: DataAuthScheme = "none"
|
|
26
|
+
"""
|
|
27
|
+
Defines how the list of allowed viewer paths is built.
|
|
28
|
+
|
|
29
|
+
This variable affects the `GET /auth/current-user/allowed-viewer-paths/`
|
|
30
|
+
response, which is then consumed by
|
|
31
|
+
[fractal-data](https://github.com/fractal-analytics-platform/fractal-data).
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
|
|
35
|
+
- "viewer-paths": The list of allowed viewer paths will include the user's
|
|
36
|
+
`project_dir` along with any path defined in user groups' `viewer_paths`
|
|
37
|
+
attributes.
|
|
38
|
+
- "users-folders": The list will consist of the user's `project_dir` and a
|
|
39
|
+
user-specific folder. The user folder is constructed by concatenating
|
|
40
|
+
the base folder `FRACTAL_DATA_BASE_FOLDER` with the user's profile
|
|
41
|
+
`username`.
|
|
42
|
+
- "none": An empty list will be returned, indicating no access to
|
|
43
|
+
viewer paths. Useful when vizarr viewer is not used.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
FRACTAL_DATA_BASE_FOLDER: AbsolutePathStr | None = None
|
|
47
|
+
"""
|
|
48
|
+
Base path to Zarr files that will be served by fractal-vizarr-viewer;
|
|
49
|
+
This variable is required and used only when
|
|
50
|
+
FRACTAL_DATA_AUTHORIZATION_SCHEME is set to "users-folders".
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
@model_validator(mode="after")
|
|
54
|
+
def check(self: Self) -> Self:
|
|
55
|
+
"""
|
|
56
|
+
`FRACTAL_DATA_BASE_FOLDER` is required when
|
|
57
|
+
`FRACTAL_DATA_AUTHORIZATION_SCHEME` is set to `"users-folders"`.
|
|
58
|
+
"""
|
|
59
|
+
if (
|
|
60
|
+
self.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.USERS_FOLDERS
|
|
61
|
+
and self.FRACTAL_DATA_BASE_FOLDER is None
|
|
62
|
+
):
|
|
63
|
+
raise ValueError(
|
|
64
|
+
"FRACTAL_DATA_BASE_FOLDER is required when "
|
|
65
|
+
"FRACTAL_DATA_AUTH_SCHEME is set to "
|
|
66
|
+
"users-folders"
|
|
67
|
+
)
|
|
68
|
+
return self
|
fractal_server/config/_main.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from enum import StrEnum
|
|
3
2
|
from typing import Literal
|
|
4
|
-
from typing import TypeVar
|
|
5
3
|
|
|
6
4
|
from pydantic import HttpUrl
|
|
7
5
|
from pydantic import SecretStr
|
|
@@ -9,20 +7,6 @@ from pydantic_settings import BaseSettings
|
|
|
9
7
|
from pydantic_settings import SettingsConfigDict
|
|
10
8
|
|
|
11
9
|
from ._settings_config import SETTINGS_CONFIG_DICT
|
|
12
|
-
from fractal_server.types import AbsolutePathStr
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class FractalConfigurationError(ValueError):
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ViewerAuthScheme(StrEnum):
|
|
20
|
-
VIEWER_PATHS = "viewer-paths"
|
|
21
|
-
USERS_FOLDERS = "users-folders"
|
|
22
|
-
NONE = "none"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
T = TypeVar("T")
|
|
26
10
|
|
|
27
11
|
|
|
28
12
|
class Settings(BaseSettings):
|
|
@@ -34,7 +18,6 @@ class Settings(BaseSettings):
|
|
|
34
18
|
|
|
35
19
|
model_config = SettingsConfigDict(**SETTINGS_CONFIG_DICT)
|
|
36
20
|
|
|
37
|
-
# JWT TOKEN
|
|
38
21
|
JWT_EXPIRE_SECONDS: int = 180
|
|
39
22
|
"""
|
|
40
23
|
JWT token lifetime, in seconds.
|
|
@@ -48,7 +31,6 @@ class Settings(BaseSettings):
|
|
|
48
31
|
it.
|
|
49
32
|
"""
|
|
50
33
|
|
|
51
|
-
# COOKIE TOKEN
|
|
52
34
|
COOKIE_EXPIRE_SECONDS: int = 86400
|
|
53
35
|
"""
|
|
54
36
|
Cookie token lifetime, in seconds.
|
|
@@ -69,7 +51,7 @@ class Settings(BaseSettings):
|
|
|
69
51
|
Only logs of with this level (or higher) will appear in the console logs.
|
|
70
52
|
"""
|
|
71
53
|
|
|
72
|
-
FRACTAL_API_MAX_JOB_LIST_LENGTH: int =
|
|
54
|
+
FRACTAL_API_MAX_JOB_LIST_LENGTH: int = 25
|
|
73
55
|
"""
|
|
74
56
|
Number of ids that can be stored in the `jobsV2` attribute of
|
|
75
57
|
`app.state`.
|
|
@@ -80,52 +62,6 @@ class Settings(BaseSettings):
|
|
|
80
62
|
Waiting time for the shutdown phase of executors
|
|
81
63
|
"""
|
|
82
64
|
|
|
83
|
-
FRACTAL_VIEWER_AUTHORIZATION_SCHEME: ViewerAuthScheme = "none"
|
|
84
|
-
"""
|
|
85
|
-
Defines how the list of allowed viewer paths is built.
|
|
86
|
-
|
|
87
|
-
This variable affects the `GET /auth/current-user/allowed-viewer-paths/`
|
|
88
|
-
response, which is then consumed by
|
|
89
|
-
[fractal-vizarr-viewer](https://github.com/fractal-analytics-platform/fractal-vizarr-viewer).
|
|
90
|
-
|
|
91
|
-
Options:
|
|
92
|
-
|
|
93
|
-
- "viewer-paths": The list of allowed viewer paths will include the user's
|
|
94
|
-
`project_dir` along with any path defined in user groups' `viewer_paths`
|
|
95
|
-
attributes.
|
|
96
|
-
- "users-folders": The list will consist of the user's `project_dir` and a
|
|
97
|
-
user-specific folder. The user folder is constructed by concatenating
|
|
98
|
-
the base folder `FRACTAL_VIEWER_BASE_FOLDER` with the user's
|
|
99
|
-
`slurm_user`.
|
|
100
|
-
- "none": An empty list will be returned, indicating no access to
|
|
101
|
-
viewer paths. Useful when vizarr viewer is not used.
|
|
102
|
-
"""
|
|
103
|
-
|
|
104
|
-
FRACTAL_VIEWER_BASE_FOLDER: AbsolutePathStr | None = None
|
|
105
|
-
"""
|
|
106
|
-
Base path to Zarr files that will be served by fractal-vizarr-viewer;
|
|
107
|
-
This variable is required and used only when
|
|
108
|
-
FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders".
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
def check(self):
|
|
112
|
-
"""
|
|
113
|
-
Make sure that required variables are set
|
|
114
|
-
|
|
115
|
-
This method must be called before the server starts
|
|
116
|
-
"""
|
|
117
|
-
# FRACTAL_VIEWER_BASE_FOLDER is required when
|
|
118
|
-
# FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders"
|
|
119
|
-
# and it must be an absolute path
|
|
120
|
-
if self.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "users-folders":
|
|
121
|
-
viewer_base_folder = self.FRACTAL_VIEWER_BASE_FOLDER
|
|
122
|
-
if viewer_base_folder is None:
|
|
123
|
-
raise FractalConfigurationError(
|
|
124
|
-
"FRACTAL_VIEWER_BASE_FOLDER is required when "
|
|
125
|
-
"FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "
|
|
126
|
-
"users-folders"
|
|
127
|
-
)
|
|
128
|
-
|
|
129
65
|
FRACTAL_HELP_URL: HttpUrl | None = None
|
|
130
66
|
"""
|
|
131
67
|
The URL of an instance-specific Fractal help page.
|
fractal_server/config/_oauth.py
CHANGED
|
@@ -37,7 +37,7 @@ class OAuthSettings(BaseSettings):
|
|
|
37
37
|
"""
|
|
38
38
|
Secret to authorise against the identity provider.
|
|
39
39
|
"""
|
|
40
|
-
OAUTH_OIDC_CONFIG_ENDPOINT:
|
|
40
|
+
OAUTH_OIDC_CONFIG_ENDPOINT: SecretStr | None = None
|
|
41
41
|
"""
|
|
42
42
|
OpenID configuration endpoint, for autodiscovery of relevant endpoints.
|
|
43
43
|
"""
|
|
@@ -55,7 +55,7 @@ class OAuthSettings(BaseSettings):
|
|
|
55
55
|
and self.OAUTH_OIDC_CONFIG_ENDPOINT is None
|
|
56
56
|
):
|
|
57
57
|
raise ValueError(
|
|
58
|
-
f"
|
|
58
|
+
f"self.OAUTH_OIDC_CONFIG_ENDPOINT=None but "
|
|
59
59
|
f"{self.OAUTH_CLIENT_NAME=}"
|
|
60
60
|
)
|
|
61
61
|
return self
|
fractal_server/main.py
CHANGED
|
@@ -6,6 +6,7 @@ from fastapi import FastAPI
|
|
|
6
6
|
|
|
7
7
|
from .app.routes.aux._runner import _backend_supports_shutdown
|
|
8
8
|
from .app.shutdown import cleanup_after_shutdown
|
|
9
|
+
from .config import get_data_settings
|
|
9
10
|
from .config import get_db_settings
|
|
10
11
|
from .config import get_email_settings
|
|
11
12
|
from .config import get_settings
|
|
@@ -50,16 +51,16 @@ def check_settings() -> None:
|
|
|
50
51
|
ValidationError: If the configuration is invalid.
|
|
51
52
|
"""
|
|
52
53
|
settings = Inject(get_settings)
|
|
53
|
-
settings.check()
|
|
54
54
|
db_settings = Inject(get_db_settings)
|
|
55
55
|
email_settings = Inject(get_email_settings)
|
|
56
|
-
|
|
56
|
+
data_settings = Inject(get_data_settings)
|
|
57
57
|
logger = set_logger("fractal_server_settings")
|
|
58
58
|
logger.debug("Fractal Settings:")
|
|
59
59
|
for key, value in chain(
|
|
60
60
|
db_settings.model_dump().items(),
|
|
61
61
|
settings.model_dump().items(),
|
|
62
62
|
email_settings.model_dump().items(),
|
|
63
|
+
data_settings.model_dump().items(),
|
|
63
64
|
):
|
|
64
65
|
if any(s in key.upper() for s in ["PASSWORD", "SECRET", "KEY"]):
|
|
65
66
|
value = "*****"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
NAMING_CONVENTION = {
|
|
2
2
|
"ix": "ix_%(column_0_label)s",
|
|
3
3
|
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
|
4
|
-
"ck": "ck_%(table_name)s_
|
|
4
|
+
"ck": "ck_%(table_name)s_%(constraint_name)s",
|
|
5
5
|
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
|
6
6
|
"pk": "pk_%(table_name)s",
|
|
7
7
|
}
|
fractal_server/migrations/versions/{a80ac5a352bf_resource_profile.py → 83bc2ad3ffcc_2_17_0.py}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""2.17.0
|
|
2
2
|
|
|
3
|
-
Revision ID:
|
|
3
|
+
Revision ID: 83bc2ad3ffcc
|
|
4
4
|
Revises: 981d588fe248
|
|
5
|
-
Create Date: 2025-10-
|
|
5
|
+
Create Date: 2025-10-30 14:16:53.639006
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
8
|
import sqlalchemy as sa
|
|
@@ -11,7 +11,7 @@ from alembic import op
|
|
|
11
11
|
from sqlalchemy.dialects import postgresql
|
|
12
12
|
|
|
13
13
|
# revision identifiers, used by Alembic.
|
|
14
|
-
revision = "
|
|
14
|
+
revision = "83bc2ad3ffcc"
|
|
15
15
|
down_revision = "981d588fe248"
|
|
16
16
|
branch_labels = None
|
|
17
17
|
depends_on = None
|
|
@@ -64,13 +64,11 @@ def upgrade() -> None:
|
|
|
64
64
|
),
|
|
65
65
|
sa.CheckConstraint(
|
|
66
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
|
-
),
|
|
67
|
+
name=op.f("ck_resource_jobs_slurm_python_worker_set"),
|
|
70
68
|
),
|
|
71
69
|
sa.CheckConstraint(
|
|
72
70
|
"type IN ('local', 'slurm_sudo', 'slurm_ssh')",
|
|
73
|
-
name=op.f("
|
|
71
|
+
name=op.f("ck_resource_correct_type"),
|
|
74
72
|
),
|
|
75
73
|
sa.PrimaryKeyConstraint("id", name=op.f("pk_resource")),
|
|
76
74
|
sa.UniqueConstraint("name", name=op.f("uq_resource_name")),
|
|
@@ -103,7 +101,7 @@ def upgrade() -> None:
|
|
|
103
101
|
["resource_id"],
|
|
104
102
|
["resource.id"],
|
|
105
103
|
name=op.f("fk_profile_resource_id_resource"),
|
|
106
|
-
ondelete="
|
|
104
|
+
ondelete="RESTRICT",
|
|
107
105
|
),
|
|
108
106
|
sa.PrimaryKeyConstraint("id", name=op.f("pk_profile")),
|
|
109
107
|
sa.UniqueConstraint("name", name=op.f("uq_profile_name")),
|
|
@@ -117,7 +115,7 @@ def upgrade() -> None:
|
|
|
117
115
|
"resource",
|
|
118
116
|
["resource_id"],
|
|
119
117
|
["id"],
|
|
120
|
-
ondelete="
|
|
118
|
+
ondelete="RESTRICT",
|
|
121
119
|
)
|
|
122
120
|
|
|
123
121
|
with op.batch_alter_table("taskgroupv2", schema=None) as batch_op:
|
|
@@ -129,52 +127,54 @@ def upgrade() -> None:
|
|
|
129
127
|
"resource",
|
|
130
128
|
["resource_id"],
|
|
131
129
|
["id"],
|
|
132
|
-
ondelete="
|
|
130
|
+
ondelete="RESTRICT",
|
|
133
131
|
)
|
|
134
132
|
|
|
135
133
|
with op.batch_alter_table("user_oauth", schema=None) as batch_op:
|
|
136
134
|
batch_op.add_column(
|
|
137
135
|
sa.Column("profile_id", sa.Integer(), nullable=True)
|
|
138
136
|
)
|
|
137
|
+
batch_op.add_column(
|
|
138
|
+
sa.Column(
|
|
139
|
+
"project_dir",
|
|
140
|
+
sa.String(),
|
|
141
|
+
server_default="/PLACEHOLDER",
|
|
142
|
+
nullable=False,
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
batch_op.add_column(
|
|
146
|
+
sa.Column(
|
|
147
|
+
"slurm_accounts",
|
|
148
|
+
postgresql.ARRAY(sa.String()),
|
|
149
|
+
server_default="{}",
|
|
150
|
+
nullable=True,
|
|
151
|
+
)
|
|
152
|
+
)
|
|
139
153
|
batch_op.create_foreign_key(
|
|
140
154
|
batch_op.f("fk_user_oauth_profile_id_profile"),
|
|
141
155
|
"profile",
|
|
142
156
|
["profile_id"],
|
|
143
157
|
["id"],
|
|
144
|
-
ondelete="
|
|
158
|
+
ondelete="RESTRICT",
|
|
145
159
|
)
|
|
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")
|
|
160
|
+
batch_op.drop_column("username")
|
|
150
161
|
|
|
151
162
|
# ### end Alembic commands ###
|
|
152
163
|
|
|
153
164
|
|
|
154
165
|
def downgrade() -> None:
|
|
155
166
|
# ### commands auto generated by Alembic - please adjust! ###
|
|
156
|
-
with op.batch_alter_table("
|
|
157
|
-
batch_op.add_column(
|
|
158
|
-
sa.Column(
|
|
159
|
-
"ssh_tasks_dir",
|
|
160
|
-
sa.VARCHAR(),
|
|
161
|
-
autoincrement=False,
|
|
162
|
-
nullable=True,
|
|
163
|
-
)
|
|
164
|
-
)
|
|
167
|
+
with op.batch_alter_table("user_oauth", schema=None) as batch_op:
|
|
165
168
|
batch_op.add_column(
|
|
166
169
|
sa.Column(
|
|
167
|
-
"
|
|
168
|
-
sa.VARCHAR(),
|
|
169
|
-
autoincrement=False,
|
|
170
|
-
nullable=True,
|
|
170
|
+
"username", sa.VARCHAR(), autoincrement=False, nullable=True
|
|
171
171
|
)
|
|
172
172
|
)
|
|
173
|
-
|
|
174
|
-
with op.batch_alter_table("user_oauth", schema=None) as batch_op:
|
|
175
173
|
batch_op.drop_constraint(
|
|
176
174
|
batch_op.f("fk_user_oauth_profile_id_profile"), type_="foreignkey"
|
|
177
175
|
)
|
|
176
|
+
batch_op.drop_column("slurm_accounts")
|
|
177
|
+
batch_op.drop_column("project_dir")
|
|
178
178
|
batch_op.drop_column("profile_id")
|
|
179
179
|
|
|
180
180
|
with op.batch_alter_table("taskgroupv2", schema=None) as batch_op:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fractal-server
|
|
3
|
-
Version: 2.17.
|
|
3
|
+
Version: 2.17.0a7
|
|
4
4
|
Summary: Backend component of the Fractal analytics platform
|
|
5
5
|
License-Expression: BSD-3-Clause
|
|
6
6
|
License-File: LICENSE
|
|
@@ -13,18 +13,17 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Requires-Dist: alembic (>=1.13.1,<2.0.0)
|
|
15
15
|
Requires-Dist: fabric (>=3.2.2,<3.3.0)
|
|
16
|
-
Requires-Dist: fastapi (>=0.
|
|
17
|
-
Requires-Dist: fastapi-users[oauth] (>=
|
|
16
|
+
Requires-Dist: fastapi (>=0.120.0,<0.121.0)
|
|
17
|
+
Requires-Dist: fastapi-users[oauth] (>=15,<16)
|
|
18
18
|
Requires-Dist: gunicorn (>=23.0,<24.0)
|
|
19
19
|
Requires-Dist: packaging (>=25.0.0,<26.0.0)
|
|
20
20
|
Requires-Dist: psycopg[binary] (>=3.1.0,<4.0.0)
|
|
21
21
|
Requires-Dist: pydantic (>=2.12.0,<2.13.0)
|
|
22
22
|
Requires-Dist: pydantic-settings (==2.11.0)
|
|
23
|
-
Requires-Dist: python-dotenv (>=1.1.0,<1.2.0)
|
|
24
23
|
Requires-Dist: sqlalchemy[asyncio] (>=2.0.23,<2.1)
|
|
25
24
|
Requires-Dist: sqlmodel (==0.0.27)
|
|
26
25
|
Requires-Dist: tomli_w (>=1.2.0,<1.3.0)
|
|
27
|
-
Requires-Dist: uvicorn (>=0.
|
|
26
|
+
Requires-Dist: uvicorn (>=0.38.0,<0.39.0)
|
|
28
27
|
Requires-Dist: uvicorn-worker (==0.4.0)
|
|
29
28
|
Project-URL: Documentation, https://fractal-analytics-platform.github.io/fractal-server
|
|
30
29
|
Project-URL: Homepage, https://github.com/fractal-analytics-platform/fractal-server
|