fractal-server 2.8.0__py3-none-any.whl → 2.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fractal_server/__init__.py +1 -1
- fractal_server/app/db/__init__.py +2 -35
- fractal_server/app/models/v2/__init__.py +3 -3
- fractal_server/app/models/v2/task.py +0 -72
- fractal_server/app/models/v2/task_group.py +113 -0
- fractal_server/app/routes/admin/v1.py +13 -30
- fractal_server/app/routes/admin/v2/__init__.py +4 -0
- fractal_server/app/routes/admin/v2/job.py +13 -24
- fractal_server/app/routes/admin/v2/task.py +13 -0
- fractal_server/app/routes/admin/v2/task_group.py +75 -14
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +267 -0
- fractal_server/app/routes/api/v1/project.py +7 -19
- fractal_server/app/routes/api/v2/__init__.py +11 -2
- fractal_server/app/routes/api/v2/{_aux_functions_task_collection.py → _aux_functions_task_lifecycle.py} +83 -0
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +27 -17
- fractal_server/app/routes/api/v2/submit.py +19 -24
- fractal_server/app/routes/api/v2/task_collection.py +33 -65
- fractal_server/app/routes/api/v2/task_collection_custom.py +3 -3
- fractal_server/app/routes/api/v2/task_group.py +86 -14
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +272 -0
- fractal_server/app/routes/api/v2/workflow.py +1 -1
- fractal_server/app/routes/api/v2/workflow_import.py +2 -2
- fractal_server/app/routes/auth/current_user.py +60 -17
- fractal_server/app/routes/auth/group.py +67 -39
- fractal_server/app/routes/auth/users.py +97 -99
- fractal_server/app/routes/aux/__init__.py +20 -0
- fractal_server/app/runner/executors/slurm/_slurm_config.py +0 -17
- fractal_server/app/runner/executors/slurm/ssh/executor.py +49 -204
- fractal_server/app/runner/executors/slurm/sudo/executor.py +26 -109
- fractal_server/app/runner/executors/slurm/utils_executors.py +58 -0
- fractal_server/app/runner/v2/_local_experimental/executor.py +2 -1
- fractal_server/app/schemas/_validators.py +1 -16
- fractal_server/app/schemas/user.py +16 -10
- fractal_server/app/schemas/user_group.py +0 -11
- fractal_server/app/schemas/v1/applyworkflow.py +0 -8
- fractal_server/app/schemas/v1/dataset.py +0 -5
- fractal_server/app/schemas/v1/project.py +0 -5
- fractal_server/app/schemas/v1/state.py +0 -5
- fractal_server/app/schemas/v1/workflow.py +0 -5
- fractal_server/app/schemas/v2/__init__.py +4 -2
- fractal_server/app/schemas/v2/dataset.py +1 -7
- fractal_server/app/schemas/v2/job.py +0 -8
- fractal_server/app/schemas/v2/project.py +0 -5
- fractal_server/app/schemas/v2/task_collection.py +13 -31
- fractal_server/app/schemas/v2/task_group.py +59 -8
- fractal_server/app/schemas/v2/workflow.py +0 -5
- fractal_server/app/security/__init__.py +17 -0
- fractal_server/config.py +61 -59
- fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +117 -0
- fractal_server/ssh/_fabric.py +156 -83
- fractal_server/string_tools.py +10 -3
- fractal_server/tasks/utils.py +2 -12
- fractal_server/tasks/v2/local/__init__.py +3 -0
- fractal_server/tasks/v2/local/_utils.py +70 -0
- fractal_server/tasks/v2/local/collect.py +291 -0
- fractal_server/tasks/v2/local/deactivate.py +218 -0
- fractal_server/tasks/v2/local/reactivate.py +159 -0
- fractal_server/tasks/v2/ssh/__init__.py +3 -0
- fractal_server/tasks/v2/ssh/_utils.py +87 -0
- fractal_server/tasks/v2/ssh/collect.py +311 -0
- fractal_server/tasks/v2/ssh/deactivate.py +253 -0
- fractal_server/tasks/v2/ssh/reactivate.py +202 -0
- fractal_server/tasks/v2/templates/{_2_preliminary_pip_operations.sh → 1_create_venv.sh} +6 -7
- fractal_server/tasks/v2/templates/{_3_pip_install.sh → 2_pip_install.sh} +8 -1
- fractal_server/tasks/v2/templates/{_4_pip_freeze.sh → 3_pip_freeze.sh} +0 -7
- fractal_server/tasks/v2/templates/{_5_pip_show.sh → 4_pip_show.sh} +5 -6
- fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +10 -0
- fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +35 -0
- fractal_server/tasks/v2/utils_background.py +42 -127
- fractal_server/tasks/v2/utils_templates.py +32 -2
- fractal_server/utils.py +4 -2
- fractal_server/zip_tools.py +21 -4
- {fractal_server-2.8.0.dist-info → fractal_server-2.9.0.dist-info}/METADATA +3 -5
- {fractal_server-2.8.0.dist-info → fractal_server-2.9.0.dist-info}/RECORD +78 -65
- fractal_server/app/models/v2/collection_state.py +0 -22
- fractal_server/tasks/v2/collection_local.py +0 -357
- fractal_server/tasks/v2/collection_ssh.py +0 -352
- fractal_server/tasks/v2/templates/_1_create_venv.sh +0 -42
- /fractal_server/tasks/v2/{database_operations.py → utils_database.py} +0 -0
- {fractal_server-2.8.0.dist-info → fractal_server-2.9.0.dist-info}/LICENSE +0 -0
- {fractal_server-2.8.0.dist-info → fractal_server-2.9.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.8.0.dist-info → fractal_server-2.9.0.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,4 @@
|
|
1
|
-
from datetime import datetime
|
2
|
-
from enum import Enum
|
3
1
|
from pathlib import Path
|
4
|
-
from typing import Any
|
5
2
|
from typing import Literal
|
6
3
|
from typing import Optional
|
7
4
|
|
@@ -11,19 +8,10 @@ from pydantic import root_validator
|
|
11
8
|
from pydantic import validator
|
12
9
|
|
13
10
|
from .._validators import valstr
|
14
|
-
from fractal_server.app.schemas._validators import valutc
|
15
11
|
from fractal_server.app.schemas.v2 import ManifestV2
|
16
12
|
from fractal_server.string_tools import validate_cmd
|
17
13
|
|
18
14
|
|
19
|
-
class CollectionStatusV2(str, Enum):
|
20
|
-
PENDING = "pending"
|
21
|
-
INSTALLING = "installing"
|
22
|
-
COLLECTING = "collecting"
|
23
|
-
FAIL = "fail"
|
24
|
-
OK = "OK"
|
25
|
-
|
26
|
-
|
27
15
|
class TaskCollectPipV2(BaseModel, extra=Extra.forbid):
|
28
16
|
"""
|
29
17
|
TaskCollectPipV2 class
|
@@ -57,16 +45,8 @@ class TaskCollectPipV2(BaseModel, extra=Extra.forbid):
|
|
57
45
|
python_version: Optional[Literal["3.9", "3.10", "3.11", "3.12"]] = None
|
58
46
|
pinned_package_versions: Optional[dict[str, str]] = None
|
59
47
|
|
60
|
-
_package = validator("package", allow_reuse=True)(valstr("package"))
|
61
|
-
_package_version = validator("package_version", allow_reuse=True)(
|
62
|
-
valstr("package_version")
|
63
|
-
)
|
64
|
-
_package_extras = validator("package_extras", allow_reuse=True)(
|
65
|
-
valstr("package_extras")
|
66
|
-
)
|
67
|
-
|
68
48
|
@validator("pinned_package_versions")
|
69
|
-
def
|
49
|
+
def pinned_package_versions_validator(cls, value):
|
70
50
|
if value is None:
|
71
51
|
return value
|
72
52
|
old_keys = list(value.keys())
|
@@ -87,6 +67,7 @@ class TaskCollectPipV2(BaseModel, extra=Extra.forbid):
|
|
87
67
|
|
88
68
|
@validator("package")
|
89
69
|
def package_validator(cls, value):
|
70
|
+
value = valstr("package")(value)
|
90
71
|
if "/" in value or value.endswith(".whl"):
|
91
72
|
if not value.endswith(".whl"):
|
92
73
|
raise ValueError(
|
@@ -97,17 +78,27 @@ class TaskCollectPipV2(BaseModel, extra=Extra.forbid):
|
|
97
78
|
raise ValueError(
|
98
79
|
f"Local-package path must be absolute: (given {value})."
|
99
80
|
)
|
81
|
+
validate_cmd(value, attribute_name="package")
|
100
82
|
return value
|
101
83
|
|
102
84
|
@validator("package_version")
|
103
|
-
def package_version_validator(
|
85
|
+
def package_version_validator(
|
86
|
+
cls, v: Optional[str], values
|
87
|
+
) -> Optional[str]:
|
104
88
|
v = valstr("package_version")(v)
|
105
89
|
if values["package"].endswith(".whl"):
|
106
90
|
raise ValueError(
|
107
91
|
"Cannot provide package version when package is a wheel file."
|
108
92
|
)
|
93
|
+
validate_cmd(v, attribute_name="package_version")
|
109
94
|
return v
|
110
95
|
|
96
|
+
@validator("package_extras")
|
97
|
+
def package_extras_validator(cls, value: Optional[str]) -> Optional[str]:
|
98
|
+
value = valstr("package_extras")(value)
|
99
|
+
validate_cmd(value, attribute_name="package_extras")
|
100
|
+
return value
|
101
|
+
|
111
102
|
|
112
103
|
class TaskCollectCustomV2(BaseModel, extra=Extra.forbid):
|
113
104
|
"""
|
@@ -188,12 +179,3 @@ class TaskCollectCustomV2(BaseModel, extra=Extra.forbid):
|
|
188
179
|
f"Python interpreter path must be absolute: (given {value})."
|
189
180
|
)
|
190
181
|
return value
|
191
|
-
|
192
|
-
|
193
|
-
class CollectionStateReadV2(BaseModel):
|
194
|
-
|
195
|
-
id: Optional[int]
|
196
|
-
data: dict[str, Any]
|
197
|
-
timestamp: datetime
|
198
|
-
|
199
|
-
_timestamp = validator("timestamp", allow_reuse=True)(valutc("timestamp"))
|
@@ -1,11 +1,11 @@
|
|
1
1
|
from datetime import datetime
|
2
2
|
from enum import Enum
|
3
|
-
from typing import Literal
|
4
3
|
from typing import Optional
|
5
4
|
|
6
5
|
from pydantic import BaseModel
|
7
6
|
from pydantic import Extra
|
8
7
|
from pydantic import Field
|
8
|
+
from pydantic import root_validator
|
9
9
|
from pydantic import validator
|
10
10
|
|
11
11
|
from .._validators import val_absolute_path
|
@@ -20,6 +20,19 @@ class TaskGroupV2OriginEnum(str, Enum):
|
|
20
20
|
OTHER = "other"
|
21
21
|
|
22
22
|
|
23
|
+
class TaskGroupActivityStatusV2(str, Enum):
|
24
|
+
PENDING = "pending"
|
25
|
+
ONGOING = "ongoing"
|
26
|
+
FAILED = "failed"
|
27
|
+
OK = "OK"
|
28
|
+
|
29
|
+
|
30
|
+
class TaskGroupActivityActionV2(str, Enum):
|
31
|
+
COLLECT = "collect"
|
32
|
+
DEACTIVATE = "deactivate"
|
33
|
+
REACTIVATE = "reactivate"
|
34
|
+
|
35
|
+
|
23
36
|
class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
|
24
37
|
user_id: int
|
25
38
|
user_group_id: Optional[int] = None
|
@@ -32,6 +45,7 @@ class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
|
|
32
45
|
venv_path: Optional[str] = None
|
33
46
|
wheel_path: Optional[str] = None
|
34
47
|
pip_extras: Optional[str] = None
|
48
|
+
pip_freeze: Optional[str] = None
|
35
49
|
pinned_package_versions: dict[str, str] = Field(default_factory=dict)
|
36
50
|
|
37
51
|
# Validators
|
@@ -53,6 +67,32 @@ class TaskGroupCreateV2(BaseModel, extra=Extra.forbid):
|
|
53
67
|
)
|
54
68
|
|
55
69
|
|
70
|
+
class TaskGroupCreateV2Strict(TaskGroupCreateV2):
|
71
|
+
"""
|
72
|
+
A strict version of TaskGroupCreateV2, to be used for task collection.
|
73
|
+
"""
|
74
|
+
|
75
|
+
path: str
|
76
|
+
venv_path: str
|
77
|
+
version: str
|
78
|
+
python_version: str
|
79
|
+
|
80
|
+
@root_validator
|
81
|
+
def check_wheel_file(cls, values):
|
82
|
+
origin = values.get("origin")
|
83
|
+
wheel_path = values.get("wheel_path")
|
84
|
+
bad_condition_1 = (
|
85
|
+
origin == TaskGroupV2OriginEnum.WHEELFILE and wheel_path is None
|
86
|
+
)
|
87
|
+
bad_condition_2 = (
|
88
|
+
origin != TaskGroupV2OriginEnum.WHEELFILE
|
89
|
+
and wheel_path is not None
|
90
|
+
)
|
91
|
+
if bad_condition_1 or bad_condition_2:
|
92
|
+
raise ValueError(f"Cannot have {origin=} and {wheel_path=}.")
|
93
|
+
return values
|
94
|
+
|
95
|
+
|
56
96
|
class TaskGroupReadV2(BaseModel):
|
57
97
|
id: int
|
58
98
|
task_list: list[TaskReadV2]
|
@@ -60,26 +100,37 @@ class TaskGroupReadV2(BaseModel):
|
|
60
100
|
user_id: int
|
61
101
|
user_group_id: Optional[int] = None
|
62
102
|
|
63
|
-
origin:
|
103
|
+
origin: TaskGroupV2OriginEnum
|
64
104
|
pkg_name: str
|
65
105
|
version: Optional[str] = None
|
66
106
|
python_version: Optional[str] = None
|
67
107
|
path: Optional[str] = None
|
68
108
|
venv_path: Optional[str] = None
|
69
109
|
wheel_path: Optional[str] = None
|
110
|
+
pip_freeze: Optional[str] = None
|
70
111
|
pip_extras: Optional[str] = None
|
71
112
|
pinned_package_versions: dict[str, str] = Field(default_factory=dict)
|
72
113
|
|
114
|
+
venv_size_in_kB: Optional[int] = None
|
115
|
+
venv_file_number: Optional[int] = None
|
116
|
+
|
73
117
|
active: bool
|
74
118
|
timestamp_created: datetime
|
119
|
+
timestamp_last_used: datetime
|
75
120
|
|
76
121
|
|
77
122
|
class TaskGroupUpdateV2(BaseModel, extra=Extra.forbid):
|
78
123
|
user_group_id: Optional[int] = None
|
79
|
-
active: Optional[bool] = None
|
80
124
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
125
|
+
|
126
|
+
class TaskGroupActivityV2Read(BaseModel):
|
127
|
+
id: int
|
128
|
+
user_id: int
|
129
|
+
taskgroupv2_id: Optional[int] = None
|
130
|
+
timestamp_started: datetime
|
131
|
+
timestamp_ended: Optional[datetime] = None
|
132
|
+
pkg_name: str
|
133
|
+
version: str
|
134
|
+
status: TaskGroupActivityStatusV2
|
135
|
+
action: TaskGroupActivityActionV2
|
136
|
+
log: Optional[str] = None
|
@@ -6,7 +6,6 @@ from pydantic import Extra
|
|
6
6
|
from pydantic import validator
|
7
7
|
|
8
8
|
from .._validators import valstr
|
9
|
-
from .._validators import valutc
|
10
9
|
from .project import ProjectReadV2
|
11
10
|
from .workflowtask import WorkflowTaskExportV2
|
12
11
|
from .workflowtask import WorkflowTaskImportV2
|
@@ -31,10 +30,6 @@ class WorkflowReadV2(BaseModel):
|
|
31
30
|
project: ProjectReadV2
|
32
31
|
timestamp_created: datetime
|
33
32
|
|
34
|
-
_timestamp_created = validator("timestamp_created", allow_reuse=True)(
|
35
|
-
valutc("timestamp_created")
|
36
|
-
)
|
37
|
-
|
38
33
|
|
39
34
|
class WorkflowReadV2WithWarnings(WorkflowReadV2):
|
40
35
|
task_list: list[WorkflowTaskReadV2WithWarning]
|
@@ -43,6 +43,9 @@ from fastapi_users.exceptions import UserAlreadyExists
|
|
43
43
|
from fastapi_users.models import ID
|
44
44
|
from fastapi_users.models import OAP
|
45
45
|
from fastapi_users.models import UP
|
46
|
+
from fastapi_users.password import PasswordHelper
|
47
|
+
from pwdlib import PasswordHash
|
48
|
+
from pwdlib.hashers.bcrypt import BcryptHasher
|
46
49
|
from sqlalchemy.ext.asyncio import AsyncSession
|
47
50
|
from sqlalchemy.orm import selectinload
|
48
51
|
from sqlmodel import func
|
@@ -177,7 +180,21 @@ async def get_user_db(
|
|
177
180
|
yield SQLModelUserDatabaseAsync(session, UserOAuth, OAuthAccount)
|
178
181
|
|
179
182
|
|
183
|
+
password_hash = PasswordHash(hashers=(BcryptHasher(),))
|
184
|
+
password_helper = PasswordHelper(password_hash=password_hash)
|
185
|
+
|
186
|
+
|
180
187
|
class UserManager(IntegerIDMixin, BaseUserManager[UserOAuth, int]):
|
188
|
+
def __init__(self, user_db):
|
189
|
+
"""
|
190
|
+
Override `__init__` of `BaseUserManager` to define custom
|
191
|
+
`password_helper`.
|
192
|
+
"""
|
193
|
+
super().__init__(
|
194
|
+
user_db=user_db,
|
195
|
+
password_helper=password_helper,
|
196
|
+
)
|
197
|
+
|
181
198
|
async def validate_password(self, password: str, user: UserOAuth) -> None:
|
182
199
|
# check password length
|
183
200
|
min_length = 4
|
fractal_server/config.py
CHANGED
@@ -16,7 +16,6 @@ import shutil
|
|
16
16
|
import sys
|
17
17
|
from os import environ
|
18
18
|
from os import getenv
|
19
|
-
from os.path import abspath
|
20
19
|
from pathlib import Path
|
21
20
|
from typing import Literal
|
22
21
|
from typing import Optional
|
@@ -88,7 +87,7 @@ class Settings(BaseSettings):
|
|
88
87
|
"""
|
89
88
|
Contains all the configuration variables for Fractal Server
|
90
89
|
|
91
|
-
The attributes of this class are set from the
|
90
|
+
The attributes of this class are set from the environment.
|
92
91
|
"""
|
93
92
|
|
94
93
|
class Config:
|
@@ -167,10 +166,6 @@ class Settings(BaseSettings):
|
|
167
166
|
###########################################################################
|
168
167
|
# DATABASE
|
169
168
|
###########################################################################
|
170
|
-
DB_ENGINE: Literal["sqlite", "postgres-psycopg"] = "sqlite"
|
171
|
-
"""
|
172
|
-
Database engine to use (supported: `sqlite`, `postgres-psycopg`).
|
173
|
-
"""
|
174
169
|
DB_ECHO: bool = False
|
175
170
|
"""
|
176
171
|
If `True`, make database operations verbose.
|
@@ -196,44 +191,21 @@ class Settings(BaseSettings):
|
|
196
191
|
Name of the PostgreSQL database to connect to.
|
197
192
|
"""
|
198
193
|
|
199
|
-
SQLITE_PATH: Optional[str]
|
200
|
-
"""
|
201
|
-
File path where the SQLite database is located (or will be located).
|
202
|
-
"""
|
203
|
-
|
204
194
|
@property
|
205
195
|
def DATABASE_ASYNC_URL(self) -> URL:
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
)
|
215
|
-
else:
|
216
|
-
if not self.SQLITE_PATH:
|
217
|
-
raise FractalConfigurationError(
|
218
|
-
"SQLITE_PATH path cannot be None"
|
219
|
-
)
|
220
|
-
sqlite_path = abspath(self.SQLITE_PATH)
|
221
|
-
url = URL.create(
|
222
|
-
drivername="sqlite+aiosqlite",
|
223
|
-
database=sqlite_path,
|
224
|
-
)
|
196
|
+
url = URL.create(
|
197
|
+
drivername="postgresql+psycopg",
|
198
|
+
username=self.POSTGRES_USER,
|
199
|
+
password=self.POSTGRES_PASSWORD,
|
200
|
+
host=self.POSTGRES_HOST,
|
201
|
+
port=self.POSTGRES_PORT,
|
202
|
+
database=self.POSTGRES_DB,
|
203
|
+
)
|
225
204
|
return url
|
226
205
|
|
227
206
|
@property
|
228
207
|
def DATABASE_SYNC_URL(self):
|
229
|
-
|
230
|
-
return self.DATABASE_ASYNC_URL.set(drivername="postgresql+psycopg")
|
231
|
-
else:
|
232
|
-
if not self.SQLITE_PATH:
|
233
|
-
raise FractalConfigurationError(
|
234
|
-
"SQLITE_PATH path cannot be None"
|
235
|
-
)
|
236
|
-
return self.DATABASE_ASYNC_URL.set(drivername="sqlite")
|
208
|
+
return self.DATABASE_ASYNC_URL.set(drivername="postgresql+psycopg")
|
237
209
|
|
238
210
|
###########################################################################
|
239
211
|
# FRACTAL SPECIFIC
|
@@ -411,7 +383,7 @@ class Settings(BaseSettings):
|
|
411
383
|
@root_validator(pre=True)
|
412
384
|
def check_tasks_python(cls, values) -> None:
|
413
385
|
"""
|
414
|
-
Perform multiple checks of the Python-
|
386
|
+
Perform multiple checks of the Python-interpreter variables.
|
415
387
|
|
416
388
|
1. Each `FRACTAL_TASKS_PYTHON_X_Y` variable must be an absolute path,
|
417
389
|
if set.
|
@@ -460,7 +432,7 @@ class Settings(BaseSettings):
|
|
460
432
|
f"{current_version_dot}"
|
461
433
|
)
|
462
434
|
|
463
|
-
# Unset all existing
|
435
|
+
# Unset all existing interpreters variable
|
464
436
|
for _version in ["3_9", "3_10", "3_11", "3_12"]:
|
465
437
|
key = f"FRACTAL_TASKS_PYTHON_{_version}"
|
466
438
|
if _version == current_version:
|
@@ -526,6 +498,36 @@ class Settings(BaseSettings):
|
|
526
498
|
Maximum value at which to update `pip` before performing task collection.
|
527
499
|
"""
|
528
500
|
|
501
|
+
FRACTAL_VIEWER_AUTHORIZATION_SCHEME: Literal[
|
502
|
+
"viewer-paths", "users-folders", "none"
|
503
|
+
] = "none"
|
504
|
+
"""
|
505
|
+
Defines how the list of allowed viewer paths is built.
|
506
|
+
|
507
|
+
This variable affects the `GET /auth/current-user/allowed-viewer-paths/`
|
508
|
+
response, which is then consumed by
|
509
|
+
[fractal-vizarr-viewer](https://github.com/fractal-analytics-platform/fractal-vizarr-viewer).
|
510
|
+
|
511
|
+
Options:
|
512
|
+
|
513
|
+
- "viewer-paths": The list of allowed viewer paths will include the user's
|
514
|
+
`project_dir` along with any path defined in user groups' `viewer_paths`
|
515
|
+
attributes.
|
516
|
+
- "users-folders": The list will consist of the user's `project_dir` and a
|
517
|
+
user-specific folder. The user folder is constructed by concatenating
|
518
|
+
the base folder `FRACTAL_VIEWER_BASE_FOLDER` with the user's
|
519
|
+
`slurm_user`.
|
520
|
+
- "none": An empty list will be returned, indicating no access to
|
521
|
+
viewer paths. Useful when vizarr viewer is not used.
|
522
|
+
"""
|
523
|
+
|
524
|
+
FRACTAL_VIEWER_BASE_FOLDER: Optional[str] = None
|
525
|
+
"""
|
526
|
+
Base path to Zarr files that will be served by fractal-vizarr-viewer;
|
527
|
+
This variable is required and used only when
|
528
|
+
FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders".
|
529
|
+
"""
|
530
|
+
|
529
531
|
###########################################################################
|
530
532
|
# BUSINESS LOGIC
|
531
533
|
###########################################################################
|
@@ -533,25 +535,8 @@ class Settings(BaseSettings):
|
|
533
535
|
"""
|
534
536
|
Checks that db environment variables are properly set.
|
535
537
|
"""
|
536
|
-
if self.
|
537
|
-
|
538
|
-
raise FractalConfigurationError(
|
539
|
-
"POSTGRES_DB cannot be None when DB_ENGINE="
|
540
|
-
"postgres-psycopg."
|
541
|
-
)
|
542
|
-
|
543
|
-
try:
|
544
|
-
import psycopg # noqa: F401
|
545
|
-
except ModuleNotFoundError:
|
546
|
-
raise FractalConfigurationError(
|
547
|
-
"DB engine is `postgres-psycopg` but `psycopg` is not "
|
548
|
-
"available"
|
549
|
-
)
|
550
|
-
else:
|
551
|
-
if not self.SQLITE_PATH:
|
552
|
-
raise FractalConfigurationError(
|
553
|
-
"SQLITE_PATH cannot be None when DB_ENGINE=sqlite."
|
554
|
-
)
|
538
|
+
if not self.POSTGRES_DB:
|
539
|
+
raise FractalConfigurationError("POSTGRES_DB cannot be None.")
|
555
540
|
|
556
541
|
def check_runner(self) -> None:
|
557
542
|
|
@@ -634,6 +619,23 @@ class Settings(BaseSettings):
|
|
634
619
|
if not self.FRACTAL_TASKS_DIR:
|
635
620
|
raise FractalConfigurationError("FRACTAL_TASKS_DIR cannot be None")
|
636
621
|
|
622
|
+
# FRACTAL_VIEWER_BASE_FOLDER is required when
|
623
|
+
# FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders"
|
624
|
+
# and it must be an absolute path
|
625
|
+
if self.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "users-folders":
|
626
|
+
viewer_base_folder = self.FRACTAL_VIEWER_BASE_FOLDER
|
627
|
+
if viewer_base_folder is None:
|
628
|
+
raise FractalConfigurationError(
|
629
|
+
"FRACTAL_VIEWER_BASE_FOLDER is required when "
|
630
|
+
"FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "
|
631
|
+
"users-folders"
|
632
|
+
)
|
633
|
+
if not Path(viewer_base_folder).is_absolute():
|
634
|
+
raise FractalConfigurationError(
|
635
|
+
f"Non-absolute value for "
|
636
|
+
f"FRACTAL_VIEWER_BASE_FOLDER={viewer_base_folder}"
|
637
|
+
)
|
638
|
+
|
637
639
|
self.check_db()
|
638
640
|
self.check_runner()
|
639
641
|
|
@@ -0,0 +1,117 @@
|
|
1
|
+
"""TaskGroup Activity and venv-info to TaskGroup
|
2
|
+
|
3
|
+
Revision ID: d256a7379ab8
|
4
|
+
Revises: 19eca0dd47a9
|
5
|
+
Create Date: 2024-11-20 15:01:52.659832
|
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 = "d256a7379ab8"
|
15
|
+
down_revision = "19eca0dd47a9"
|
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
|
+
"taskgroupactivityv2",
|
24
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
25
|
+
sa.Column("user_id", sa.Integer(), nullable=False),
|
26
|
+
sa.Column("taskgroupv2_id", sa.Integer(), nullable=True),
|
27
|
+
sa.Column(
|
28
|
+
"timestamp_started", sa.DateTime(timezone=True), nullable=False
|
29
|
+
),
|
30
|
+
sa.Column(
|
31
|
+
"pkg_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False
|
32
|
+
),
|
33
|
+
sa.Column(
|
34
|
+
"version", sqlmodel.sql.sqltypes.AutoString(), nullable=False
|
35
|
+
),
|
36
|
+
sa.Column(
|
37
|
+
"status", sqlmodel.sql.sqltypes.AutoString(), nullable=False
|
38
|
+
),
|
39
|
+
sa.Column(
|
40
|
+
"action", sqlmodel.sql.sqltypes.AutoString(), nullable=False
|
41
|
+
),
|
42
|
+
sa.Column("log", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
43
|
+
sa.Column(
|
44
|
+
"timestamp_ended", sa.DateTime(timezone=True), nullable=True
|
45
|
+
),
|
46
|
+
sa.ForeignKeyConstraint(
|
47
|
+
["taskgroupv2_id"],
|
48
|
+
["taskgroupv2.id"],
|
49
|
+
name=op.f("fk_taskgroupactivityv2_taskgroupv2_id_taskgroupv2"),
|
50
|
+
),
|
51
|
+
sa.ForeignKeyConstraint(
|
52
|
+
["user_id"],
|
53
|
+
["user_oauth.id"],
|
54
|
+
name=op.f("fk_taskgroupactivityv2_user_id_user_oauth"),
|
55
|
+
),
|
56
|
+
sa.PrimaryKeyConstraint("id", name=op.f("pk_taskgroupactivityv2")),
|
57
|
+
)
|
58
|
+
op.drop_table("collectionstatev2")
|
59
|
+
with op.batch_alter_table("taskgroupv2", schema=None) as batch_op:
|
60
|
+
batch_op.add_column(
|
61
|
+
sa.Column(
|
62
|
+
"pip_freeze", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
63
|
+
)
|
64
|
+
)
|
65
|
+
batch_op.add_column(
|
66
|
+
sa.Column("venv_size_in_kB", sa.Integer(), nullable=True)
|
67
|
+
)
|
68
|
+
batch_op.add_column(
|
69
|
+
sa.Column("venv_file_number", sa.Integer(), nullable=True)
|
70
|
+
)
|
71
|
+
batch_op.add_column(
|
72
|
+
sa.Column(
|
73
|
+
"timestamp_last_used",
|
74
|
+
sa.DateTime(timezone=True),
|
75
|
+
server_default="2024-11-20T00:00:00+00:00",
|
76
|
+
nullable=False,
|
77
|
+
)
|
78
|
+
)
|
79
|
+
|
80
|
+
# ### end Alembic commands ###
|
81
|
+
|
82
|
+
|
83
|
+
def downgrade() -> None:
|
84
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
85
|
+
with op.batch_alter_table("taskgroupv2", schema=None) as batch_op:
|
86
|
+
batch_op.drop_column("timestamp_last_used")
|
87
|
+
batch_op.drop_column("venv_file_number")
|
88
|
+
batch_op.drop_column("venv_size_in_kB")
|
89
|
+
batch_op.drop_column("pip_freeze")
|
90
|
+
|
91
|
+
op.create_table(
|
92
|
+
"collectionstatev2",
|
93
|
+
sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
|
94
|
+
sa.Column(
|
95
|
+
"data",
|
96
|
+
postgresql.JSON(astext_type=sa.Text()),
|
97
|
+
autoincrement=False,
|
98
|
+
nullable=True,
|
99
|
+
),
|
100
|
+
sa.Column(
|
101
|
+
"timestamp",
|
102
|
+
postgresql.TIMESTAMP(timezone=True),
|
103
|
+
autoincrement=False,
|
104
|
+
nullable=True,
|
105
|
+
),
|
106
|
+
sa.Column(
|
107
|
+
"taskgroupv2_id", sa.INTEGER(), autoincrement=False, nullable=True
|
108
|
+
),
|
109
|
+
sa.ForeignKeyConstraint(
|
110
|
+
["taskgroupv2_id"],
|
111
|
+
["taskgroupv2.id"],
|
112
|
+
name="fk_collectionstatev2_taskgroupv2_id_taskgroupv2",
|
113
|
+
),
|
114
|
+
sa.PrimaryKeyConstraint("id", name="pk_collectionstatev2"),
|
115
|
+
)
|
116
|
+
op.drop_table("taskgroupactivityv2")
|
117
|
+
# ### end Alembic commands ###
|