fractal-server 2.16.5__py3-none-any.whl → 2.17.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/__main__.py +178 -52
- fractal_server/app/db/__init__.py +9 -11
- fractal_server/app/models/security.py +30 -22
- fractal_server/app/models/user_settings.py +5 -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 +5 -0
- fractal_server/app/models/v2/resource.py +130 -0
- fractal_server/app/models/v2/task_group.py +4 -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/accounting.py +3 -3
- fractal_server/app/routes/admin/v2/impersonate.py +2 -2
- fractal_server/app/routes/admin/v2/job.py +51 -15
- fractal_server/app/routes/admin/v2/profile.py +100 -0
- fractal_server/app/routes/admin/v2/project.py +2 -2
- fractal_server/app/routes/admin/v2/resource.py +222 -0
- fractal_server/app/routes/admin/v2/task.py +59 -32
- fractal_server/app/routes/admin/v2/task_group.py +17 -12
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +52 -86
- fractal_server/app/routes/api/__init__.py +45 -8
- fractal_server/app/routes/api/v2/_aux_functions.py +17 -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 +55 -19
- fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +21 -17
- fractal_server/app/routes/api/v2/dataset.py +10 -19
- fractal_server/app/routes/api/v2/history.py +8 -8
- fractal_server/app/routes/api/v2/images.py +5 -5
- fractal_server/app/routes/api/v2/job.py +8 -8
- fractal_server/app/routes/api/v2/pre_submission_checks.py +3 -3
- fractal_server/app/routes/api/v2/project.py +15 -7
- fractal_server/app/routes/api/v2/status_legacy.py +2 -2
- fractal_server/app/routes/api/v2/submit.py +49 -42
- fractal_server/app/routes/api/v2/task.py +26 -8
- fractal_server/app/routes/api/v2/task_collection.py +39 -50
- fractal_server/app/routes/api/v2/task_collection_custom.py +10 -6
- fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -42
- fractal_server/app/routes/api/v2/task_group.py +19 -9
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +43 -86
- fractal_server/app/routes/api/v2/task_version_update.py +3 -3
- fractal_server/app/routes/api/v2/workflow.py +9 -9
- fractal_server/app/routes/api/v2/workflow_import.py +29 -16
- fractal_server/app/routes/api/v2/workflowtask.py +5 -5
- fractal_server/app/routes/auth/__init__.py +34 -5
- fractal_server/app/routes/auth/_aux_auth.py +39 -20
- fractal_server/app/routes/auth/current_user.py +56 -67
- fractal_server/app/routes/auth/group.py +29 -46
- fractal_server/app/routes/auth/oauth.py +55 -38
- fractal_server/app/routes/auth/register.py +2 -2
- fractal_server/app/routes/auth/router.py +4 -2
- fractal_server/app/routes/auth/users.py +29 -53
- fractal_server/app/routes/aux/_runner.py +2 -1
- fractal_server/app/routes/aux/validate_user_profile.py +62 -0
- fractal_server/app/schemas/__init__.py +0 -1
- fractal_server/app/schemas/user.py +43 -13
- fractal_server/app/schemas/user_group.py +2 -1
- fractal_server/app/schemas/v2/__init__.py +12 -0
- fractal_server/app/schemas/v2/profile.py +78 -0
- fractal_server/app/schemas/v2/resource.py +137 -0
- fractal_server/app/schemas/v2/task_collection.py +11 -3
- fractal_server/app/schemas/v2/task_group.py +5 -0
- fractal_server/app/security/__init__.py +174 -75
- fractal_server/app/security/signup_email.py +52 -34
- fractal_server/config/__init__.py +27 -0
- fractal_server/config/_data.py +68 -0
- fractal_server/config/_database.py +59 -0
- fractal_server/config/_email.py +133 -0
- fractal_server/config/_main.py +78 -0
- fractal_server/config/_oauth.py +69 -0
- fractal_server/config/_settings_config.py +7 -0
- fractal_server/data_migrations/2_17_0.py +339 -0
- fractal_server/images/tools.py +3 -3
- fractal_server/logger.py +3 -3
- fractal_server/main.py +17 -23
- fractal_server/migrations/naming_convention.py +1 -1
- fractal_server/migrations/versions/83bc2ad3ffcc_2_17_0.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 +129 -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 +60 -26
- fractal_server/runner/executors/slurm_common/get_slurm_config.py +39 -55
- fractal_server/runner/executors/slurm_common/remote.py +1 -1
- fractal_server/runner/executors/slurm_common/slurm_config.py +214 -0
- 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 +41 -4
- fractal_server/runner/v2/_slurm_sudo.py +42 -12
- 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 +88 -109
- 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 +5 -1
- 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_pixi.py +3 -0
- 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.0.dist-info}/METADATA +8 -10
- {fractal_server-2.16.5.dist-info → fractal_server-2.17.0.dist-info}/RECORD +137 -118
- {fractal_server-2.16.5.dist-info → fractal_server-2.17.0.dist-info}/WHEEL +1 -1
- fractal_server/app/routes/aux/validate_user_settings.py +0 -73
- fractal_server/app/schemas/user_settings.py +0 -67
- fractal_server/app/user_settings.py +0 -42
- fractal_server/config.py +0 -906
- fractal_server/data_migrations/2_14_10.py +0 -48
- fractal_server/runner/executors/slurm_common/_slurm_config.py +0 -471
- /fractal_server/{runner → app}/shutdown.py +0 -0
- {fractal_server-2.16.5.dist-info → fractal_server-2.17.0.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.16.5.dist-info → fractal_server-2.17.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -8,35 +8,53 @@ the individual backends.
|
|
|
8
8
|
import os
|
|
9
9
|
import traceback
|
|
10
10
|
from pathlib import Path
|
|
11
|
+
from typing import Protocol
|
|
11
12
|
|
|
12
13
|
from sqlalchemy.orm import Session as DBSyncSession
|
|
13
14
|
|
|
14
|
-
from ...config import get_settings
|
|
15
|
-
from ...logger import get_logger
|
|
16
|
-
from ...logger import reset_logger_handlers
|
|
17
|
-
from ...logger import set_logger
|
|
18
|
-
from ...ssh._fabric import FractalSSH
|
|
19
|
-
from ...syringe import Inject
|
|
20
|
-
from ...utils import get_timestamp
|
|
21
|
-
from ...zip_tools import _zip_folder_to_file_and_remove
|
|
22
|
-
from ..exceptions import JobExecutionError
|
|
23
|
-
from ..filenames import WORKFLOW_LOG_FILENAME
|
|
24
15
|
from ._local import process_workflow as local_process_workflow
|
|
25
16
|
from ._slurm_ssh import process_workflow as slurm_ssh_process_workflow
|
|
26
17
|
from ._slurm_sudo import process_workflow as slurm_sudo_process_workflow
|
|
27
18
|
from fractal_server import __VERSION__
|
|
28
19
|
from fractal_server.app.db import DB
|
|
29
|
-
from fractal_server.app.models import UserSettings
|
|
30
20
|
from fractal_server.app.models.v2 import DatasetV2
|
|
31
21
|
from fractal_server.app.models.v2 import JobV2
|
|
22
|
+
from fractal_server.app.models.v2 import Profile
|
|
23
|
+
from fractal_server.app.models.v2 import Resource
|
|
32
24
|
from fractal_server.app.models.v2 import WorkflowV2
|
|
33
25
|
from fractal_server.app.schemas.v2 import JobStatusTypeV2
|
|
26
|
+
from fractal_server.app.schemas.v2 import ResourceType
|
|
27
|
+
from fractal_server.logger import get_logger
|
|
28
|
+
from fractal_server.logger import reset_logger_handlers
|
|
29
|
+
from fractal_server.logger import set_logger
|
|
30
|
+
from fractal_server.runner.exceptions import JobExecutionError
|
|
31
|
+
from fractal_server.runner.filenames import WORKFLOW_LOG_FILENAME
|
|
32
|
+
from fractal_server.ssh._fabric import FractalSSH
|
|
33
|
+
from fractal_server.types import AttributeFilters
|
|
34
|
+
from fractal_server.utils import get_timestamp
|
|
35
|
+
from fractal_server.zip_tools import _zip_folder_to_file_and_remove
|
|
34
36
|
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
class ProcessWorkflowType(Protocol):
|
|
39
|
+
def __call__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
workflow: WorkflowV2,
|
|
43
|
+
dataset: DatasetV2,
|
|
44
|
+
workflow_dir_local: Path,
|
|
45
|
+
job_id: int,
|
|
46
|
+
workflow_dir_remote: Path | None,
|
|
47
|
+
first_task_index: int | None,
|
|
48
|
+
last_task_index: int | None,
|
|
49
|
+
logger_name: str,
|
|
50
|
+
job_attribute_filters: AttributeFilters,
|
|
51
|
+
job_type_filters: dict[str, bool],
|
|
52
|
+
user_id: int,
|
|
53
|
+
resource: Resource,
|
|
54
|
+
profile: Profile,
|
|
55
|
+
user_cache_dir: str,
|
|
56
|
+
) -> None:
|
|
57
|
+
...
|
|
40
58
|
|
|
41
59
|
|
|
42
60
|
def fail_job(
|
|
@@ -67,10 +85,10 @@ def submit_workflow(
|
|
|
67
85
|
dataset_id: int,
|
|
68
86
|
job_id: int,
|
|
69
87
|
user_id: int,
|
|
70
|
-
|
|
88
|
+
user_cache_dir: str,
|
|
89
|
+
resource: Resource,
|
|
90
|
+
profile: Profile,
|
|
71
91
|
worker_init: str | None = None,
|
|
72
|
-
slurm_user: str | None = None,
|
|
73
|
-
user_cache_dir: str | None = None,
|
|
74
92
|
fractal_ssh: FractalSSH | None = None,
|
|
75
93
|
) -> None:
|
|
76
94
|
"""
|
|
@@ -93,16 +111,18 @@ def submit_workflow(
|
|
|
93
111
|
Custom executor parameters that get parsed before the execution of
|
|
94
112
|
each task.
|
|
95
113
|
user_cache_dir:
|
|
96
|
-
Cache directory (namely a path where the user can write)
|
|
97
|
-
|
|
98
|
-
`job.working_dir_user`.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
114
|
+
Cache directory (namely a path where the user can write). For
|
|
115
|
+
`slurm_sudo` backend, this is both a base directory for
|
|
116
|
+
`job.working_dir_user`. For `slurm_sudo` and `slurm_ssh` backends,
|
|
117
|
+
this is used for `user_local_exports`.
|
|
118
|
+
resource:
|
|
119
|
+
Computational resource to be used for this job (e.g. a SLURM
|
|
120
|
+
cluster).
|
|
121
|
+
profile:
|
|
122
|
+
Computational profile to be used for this job.
|
|
123
|
+
fractal_ssh: SSH object, for when `resource.type = "slurm_ssh"`.
|
|
102
124
|
"""
|
|
103
125
|
# Declare runner backend and set `process_workflow` function
|
|
104
|
-
settings = Inject(get_settings)
|
|
105
|
-
FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
|
|
106
126
|
logger_name = f"WF{workflow_id}_job{job_id}"
|
|
107
127
|
logger = set_logger(logger_name=logger_name)
|
|
108
128
|
|
|
@@ -135,66 +155,30 @@ def submit_workflow(
|
|
|
135
155
|
)
|
|
136
156
|
return
|
|
137
157
|
|
|
138
|
-
# Declare runner backend and set `process_workflow` function
|
|
139
|
-
settings = Inject(get_settings)
|
|
140
|
-
FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
|
|
141
158
|
try:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
# Define and create server-side working folder
|
|
157
|
-
WORKFLOW_DIR_LOCAL = Path(job.working_dir)
|
|
158
|
-
if WORKFLOW_DIR_LOCAL.exists():
|
|
159
|
-
fail_job(
|
|
160
|
-
db=db_sync,
|
|
161
|
-
job=job,
|
|
162
|
-
log_msg=f"Workflow dir {WORKFLOW_DIR_LOCAL} already exists.",
|
|
163
|
-
logger_name=logger_name,
|
|
164
|
-
emit_log=True,
|
|
165
|
-
)
|
|
166
|
-
return
|
|
159
|
+
# Define local/remote folders, and create local folder
|
|
160
|
+
local_job_dir = Path(job.working_dir)
|
|
161
|
+
remote_job_dir = Path(job.working_dir_user)
|
|
162
|
+
match resource.type:
|
|
163
|
+
case ResourceType.LOCAL:
|
|
164
|
+
local_job_dir.mkdir(parents=True, exist_ok=False)
|
|
165
|
+
case ResourceType.SLURM_SUDO:
|
|
166
|
+
original_umask = os.umask(0)
|
|
167
|
+
local_job_dir.mkdir(
|
|
168
|
+
parents=True, mode=0o755, exist_ok=False
|
|
169
|
+
)
|
|
170
|
+
os.umask(original_umask)
|
|
171
|
+
case ResourceType.SLURM_SSH:
|
|
172
|
+
local_job_dir.mkdir(parents=True, exist_ok=False)
|
|
167
173
|
|
|
168
|
-
try:
|
|
169
|
-
# Create WORKFLOW_DIR_LOCAL and define WORKFLOW_DIR_REMOTE
|
|
170
|
-
if FRACTAL_RUNNER_BACKEND == "local":
|
|
171
|
-
WORKFLOW_DIR_LOCAL.mkdir(parents=True)
|
|
172
|
-
WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
|
|
173
|
-
elif FRACTAL_RUNNER_BACKEND == "slurm":
|
|
174
|
-
original_umask = os.umask(0)
|
|
175
|
-
WORKFLOW_DIR_LOCAL.mkdir(parents=True, mode=0o755)
|
|
176
|
-
os.umask(original_umask)
|
|
177
|
-
WORKFLOW_DIR_REMOTE = (
|
|
178
|
-
Path(user_cache_dir) / WORKFLOW_DIR_LOCAL.name
|
|
179
|
-
)
|
|
180
|
-
elif FRACTAL_RUNNER_BACKEND == "slurm_ssh":
|
|
181
|
-
WORKFLOW_DIR_LOCAL.mkdir(parents=True)
|
|
182
|
-
WORKFLOW_DIR_REMOTE = (
|
|
183
|
-
Path(user_settings.ssh_jobs_dir) / WORKFLOW_DIR_LOCAL.name
|
|
184
|
-
)
|
|
185
|
-
else:
|
|
186
|
-
raise ValueError(
|
|
187
|
-
"Invalid FRACTAL_RUNNER_BACKEND="
|
|
188
|
-
f"{settings.FRACTAL_RUNNER_BACKEND}."
|
|
189
|
-
)
|
|
190
174
|
except Exception as e:
|
|
191
175
|
error_type = type(e).__name__
|
|
192
176
|
fail_job(
|
|
193
177
|
db=db_sync,
|
|
194
178
|
job=job,
|
|
195
179
|
log_msg=(
|
|
196
|
-
f"{error_type} error
|
|
197
|
-
f"
|
|
180
|
+
f"{error_type} error while creating local job folder."
|
|
181
|
+
f" Original error: {str(e)}"
|
|
198
182
|
),
|
|
199
183
|
logger_name=logger_name,
|
|
200
184
|
emit_log=True,
|
|
@@ -218,7 +202,7 @@ def submit_workflow(
|
|
|
218
202
|
db_sync.refresh(wftask)
|
|
219
203
|
|
|
220
204
|
# Write logs
|
|
221
|
-
log_file_path =
|
|
205
|
+
log_file_path = local_job_dir / WORKFLOW_LOG_FILENAME
|
|
222
206
|
logger = set_logger(
|
|
223
207
|
logger_name=logger_name,
|
|
224
208
|
log_file_path=log_file_path,
|
|
@@ -228,14 +212,10 @@ def submit_workflow(
|
|
|
228
212
|
f"more logs at {str(log_file_path)}"
|
|
229
213
|
)
|
|
230
214
|
logger.debug(f"fractal_server.__VERSION__: {__VERSION__}")
|
|
231
|
-
logger.debug(f"
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
logger.debug(f"worker_init: {worker_init}")
|
|
236
|
-
elif FRACTAL_RUNNER_BACKEND == "slurm_ssh":
|
|
237
|
-
logger.debug(f"ssh_user: {user_settings.ssh_username}")
|
|
238
|
-
logger.debug(f"base dir: {user_settings.ssh_tasks_dir}")
|
|
215
|
+
logger.debug(f"Resource name: {resource.name}")
|
|
216
|
+
logger.debug(f"Profile name: {profile.name}")
|
|
217
|
+
logger.debug(f"Username: {profile.username}")
|
|
218
|
+
if resource.type in [ResourceType.SLURM_SUDO, ResourceType.SLURM_SSH]:
|
|
239
219
|
logger.debug(f"slurm_account: {job.slurm_account}")
|
|
240
220
|
logger.debug(f"worker_init: {worker_init}")
|
|
241
221
|
logger.debug(f"job.id: {job.id}")
|
|
@@ -247,40 +227,39 @@ def submit_workflow(
|
|
|
247
227
|
job_working_dir = job.working_dir
|
|
248
228
|
|
|
249
229
|
try:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
else:
|
|
267
|
-
raise RuntimeError(
|
|
268
|
-
f"Invalid runner backend {FRACTAL_RUNNER_BACKEND=}"
|
|
269
|
-
)
|
|
230
|
+
process_workflow: ProcessWorkflowType
|
|
231
|
+
match resource.type:
|
|
232
|
+
case ResourceType.LOCAL:
|
|
233
|
+
process_workflow = local_process_workflow
|
|
234
|
+
backend_specific_kwargs = {}
|
|
235
|
+
case ResourceType.SLURM_SUDO:
|
|
236
|
+
process_workflow = slurm_sudo_process_workflow
|
|
237
|
+
backend_specific_kwargs = dict(
|
|
238
|
+
slurm_account=job.slurm_account,
|
|
239
|
+
)
|
|
240
|
+
case ResourceType.SLURM_SSH:
|
|
241
|
+
process_workflow = slurm_ssh_process_workflow
|
|
242
|
+
backend_specific_kwargs = dict(
|
|
243
|
+
fractal_ssh=fractal_ssh,
|
|
244
|
+
slurm_account=job.slurm_account,
|
|
245
|
+
)
|
|
270
246
|
|
|
271
247
|
process_workflow(
|
|
272
248
|
workflow=workflow,
|
|
273
249
|
dataset=dataset,
|
|
274
250
|
job_id=job_id,
|
|
275
251
|
user_id=user_id,
|
|
276
|
-
workflow_dir_local=
|
|
277
|
-
workflow_dir_remote=
|
|
252
|
+
workflow_dir_local=local_job_dir,
|
|
253
|
+
workflow_dir_remote=remote_job_dir,
|
|
278
254
|
logger_name=logger_name,
|
|
279
255
|
worker_init=worker_init,
|
|
280
256
|
first_task_index=job.first_task_index,
|
|
281
257
|
last_task_index=job.last_task_index,
|
|
282
258
|
job_attribute_filters=job.attribute_filters,
|
|
283
259
|
job_type_filters=job.type_filters,
|
|
260
|
+
resource=resource,
|
|
261
|
+
profile=profile,
|
|
262
|
+
user_cache_dir=user_cache_dir,
|
|
284
263
|
**backend_specific_kwargs,
|
|
285
264
|
)
|
|
286
265
|
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import sys
|
|
3
|
+
from typing import TypedDict
|
|
3
4
|
|
|
4
5
|
import fractal_server
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
class VersionsType(TypedDict):
|
|
9
|
+
python: tuple[int, ...]
|
|
10
|
+
fractal_server: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_versions() -> VersionsType:
|
|
8
14
|
"""
|
|
9
15
|
Extract versions of Python and fractal-server.
|
|
10
16
|
"""
|
|
11
|
-
|
|
17
|
+
return dict(
|
|
12
18
|
python=tuple(sys.version_info[:3]),
|
|
13
19
|
fractal_server=fractal_server.__VERSION__,
|
|
14
20
|
)
|
|
15
|
-
return versions
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
if __name__ == "__main__":
|
fractal_server/ssh/_fabric.py
CHANGED
|
@@ -84,7 +84,7 @@ def _acquire_lock_with_timeout(
|
|
|
84
84
|
"""
|
|
85
85
|
Given a `threading.Lock` object, try to acquire it within a given timeout.
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
Args:
|
|
88
88
|
lock:
|
|
89
89
|
label:
|
|
90
90
|
timeout:
|
|
@@ -177,7 +177,7 @@ class FractalSSH:
|
|
|
177
177
|
"""
|
|
178
178
|
Log and re-raise an exception from a FractalSSH method.
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
Args:
|
|
181
181
|
message: Additional message to be logged.
|
|
182
182
|
e: Original exception
|
|
183
183
|
"""
|
|
@@ -686,7 +686,7 @@ class FractalSSHList:
|
|
|
686
686
|
|
|
687
687
|
Note: Changing `_data` requires acquiring `_lock`.
|
|
688
688
|
|
|
689
|
-
|
|
689
|
+
Args:
|
|
690
690
|
host:
|
|
691
691
|
user:
|
|
692
692
|
key_path:
|
|
@@ -732,7 +732,7 @@ class FractalSSHList:
|
|
|
732
732
|
"""
|
|
733
733
|
Return whether a given key is present in the collection.
|
|
734
734
|
|
|
735
|
-
|
|
735
|
+
Args:
|
|
736
736
|
host:
|
|
737
737
|
user:
|
|
738
738
|
key_path:
|
|
@@ -752,7 +752,7 @@ class FractalSSHList:
|
|
|
752
752
|
|
|
753
753
|
Note: Changing `_data` requires acquiring `_lock`.
|
|
754
754
|
|
|
755
|
-
|
|
755
|
+
Args:
|
|
756
756
|
host:
|
|
757
757
|
user:
|
|
758
758
|
key_path:
|
|
@@ -780,7 +780,7 @@ class FractalSSHList:
|
|
|
780
780
|
"""
|
|
781
781
|
Close all `FractalSSH` objects in the collection.
|
|
782
782
|
|
|
783
|
-
|
|
783
|
+
Args:
|
|
784
784
|
timeout:
|
|
785
785
|
Timeout for `FractalSSH._lock` acquisition, to be obtained
|
|
786
786
|
before closing.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
from pydantic import AfterValidator
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from pydantic import model_validator
|
|
7
|
+
from pydantic import PositiveInt
|
|
8
|
+
|
|
9
|
+
from fractal_server.types import DictStrStr
|
|
10
|
+
from fractal_server.types import NonEmptyStr
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _check_pixi_slurm_memory(mem: str) -> str:
|
|
14
|
+
if mem[-1] not in ["K", "M", "G", "T"]:
|
|
15
|
+
raise ValueError(
|
|
16
|
+
f"Invalid memory requirement {mem=} for `pixi`, "
|
|
17
|
+
"please set a K/M/G/T units suffix."
|
|
18
|
+
)
|
|
19
|
+
return mem
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PixiSLURMConfig(BaseModel):
|
|
23
|
+
"""
|
|
24
|
+
Parameters that are passed directly to a `sbatch` command.
|
|
25
|
+
|
|
26
|
+
See https://slurm.schedmd.com/sbatch.html.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
partition: NonEmptyStr
|
|
30
|
+
"""
|
|
31
|
+
`-p, --partition=<partition_names>`
|
|
32
|
+
"""
|
|
33
|
+
cpus: PositiveInt
|
|
34
|
+
"""
|
|
35
|
+
`-c, --cpus-per-task=<ncpus>
|
|
36
|
+
"""
|
|
37
|
+
mem: Annotated[NonEmptyStr, AfterValidator(_check_pixi_slurm_memory)]
|
|
38
|
+
"""
|
|
39
|
+
`--mem=<size>[units]` (examples: `"10M"`, `"10G"`).
|
|
40
|
+
From `sbatch` docs: Specify the real memory required per node. Default
|
|
41
|
+
units are megabytes. Different units can be specified using the suffix
|
|
42
|
+
[K|M|G|T].
|
|
43
|
+
"""
|
|
44
|
+
time: NonEmptyStr
|
|
45
|
+
"""
|
|
46
|
+
`-t, --time=<time>`.
|
|
47
|
+
From `sbatch` docs: "A time limit of zero requests that no time limit be
|
|
48
|
+
imposed. Acceptable time formats include "minutes", "minutes:seconds",
|
|
49
|
+
"hours:minutes:seconds", "days-hours", "days-hours:minutes" and
|
|
50
|
+
"days-hours:minutes:seconds".
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TasksPixiSettings(BaseModel):
|
|
55
|
+
"""
|
|
56
|
+
Configuration for `pixi` Task collection.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
versions: DictStrStr
|
|
60
|
+
"""
|
|
61
|
+
Dictionary mapping `pixi` versions (e.g. `0.47.0`) to the corresponding
|
|
62
|
+
folders (e.g. `/somewhere/pixi/0.47.0` - if the binary is
|
|
63
|
+
`/somewhere/pixi/0.47.0/bin/pixi`).
|
|
64
|
+
"""
|
|
65
|
+
default_version: str
|
|
66
|
+
"""
|
|
67
|
+
Default task-collection `pixi` version.
|
|
68
|
+
"""
|
|
69
|
+
PIXI_CONCURRENT_SOLVES: int = 4
|
|
70
|
+
"""
|
|
71
|
+
Value of
|
|
72
|
+
[`--concurrent-solves`](https://pixi.sh/latest/reference/cli/pixi/install/#arg---concurrent-solves)
|
|
73
|
+
for `pixi install`.
|
|
74
|
+
"""
|
|
75
|
+
PIXI_CONCURRENT_DOWNLOADS: int = 4
|
|
76
|
+
"""
|
|
77
|
+
Value of
|
|
78
|
+
[`--concurrent-downloads`](https://pixi.sh/latest/reference/cli/pixi/install/#arg---concurrent-downloads)
|
|
79
|
+
for `pixi install`.
|
|
80
|
+
"""
|
|
81
|
+
TOKIO_WORKER_THREADS: int = 2
|
|
82
|
+
"""
|
|
83
|
+
From
|
|
84
|
+
[Tokio documentation](
|
|
85
|
+
https://docs.rs/tokio/latest/tokio/#cpu-bound-tasks-and-blocking-code
|
|
86
|
+
)
|
|
87
|
+
:
|
|
88
|
+
|
|
89
|
+
The core threads are where all asynchronous code runs,
|
|
90
|
+
and Tokio will by default spawn one for each CPU core.
|
|
91
|
+
You can use the environment variable `TOKIO_WORKER_THREADS` to override
|
|
92
|
+
the default value.
|
|
93
|
+
"""
|
|
94
|
+
DEFAULT_ENVIRONMENT: str = "default"
|
|
95
|
+
"""
|
|
96
|
+
Default pixi environment name.
|
|
97
|
+
"""
|
|
98
|
+
DEFAULT_PLATFORM: str = "linux-64"
|
|
99
|
+
"""
|
|
100
|
+
Default platform for pixi.
|
|
101
|
+
"""
|
|
102
|
+
SLURM_CONFIG: PixiSLURMConfig | None = None
|
|
103
|
+
"""
|
|
104
|
+
Required when using `pixi` in a SSH/SLURM deployment.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
@model_validator(mode="after")
|
|
108
|
+
def check_pixi_settings(self):
|
|
109
|
+
if self.default_version not in self.versions:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"Default version '{self.default_version}' not in "
|
|
112
|
+
f"available version {list(self.versions.keys())}."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
pixi_base_dir = Path(self.versions[self.default_version]).parent
|
|
116
|
+
|
|
117
|
+
for key, value in self.versions.items():
|
|
118
|
+
pixi_path = Path(value)
|
|
119
|
+
|
|
120
|
+
if pixi_path.parent != pixi_base_dir:
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"{pixi_path=} is not located within the {pixi_base_dir=}."
|
|
123
|
+
)
|
|
124
|
+
if pixi_path.name != key:
|
|
125
|
+
raise ValueError(f"{pixi_path.name=} is not equal to {key=}")
|
|
126
|
+
|
|
127
|
+
return self
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
from typing import Self
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from pydantic import model_validator
|
|
6
|
+
|
|
7
|
+
from fractal_server.types import AbsolutePathStr
|
|
8
|
+
from fractal_server.types import NonEmptyStr
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TasksPythonSettings(BaseModel):
|
|
12
|
+
"""
|
|
13
|
+
Configuration for the Python base interpreters to be used for task venvs.
|
|
14
|
+
|
|
15
|
+
For task collection to work, there must be one or more base Python
|
|
16
|
+
interpreters available on your system.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
default_version: NonEmptyStr
|
|
20
|
+
"""
|
|
21
|
+
Default task-collection Python version (must be a key of `versions`).
|
|
22
|
+
"""
|
|
23
|
+
versions: dict[
|
|
24
|
+
Literal[
|
|
25
|
+
"3.9",
|
|
26
|
+
"3.10",
|
|
27
|
+
"3.11",
|
|
28
|
+
"3.12",
|
|
29
|
+
"3.13",
|
|
30
|
+
"3.14",
|
|
31
|
+
],
|
|
32
|
+
AbsolutePathStr,
|
|
33
|
+
]
|
|
34
|
+
"""
|
|
35
|
+
Dictionary mapping Python versions to the corresponding interpreters.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
pip_cache_dir: AbsolutePathStr | None = None
|
|
39
|
+
"""
|
|
40
|
+
Argument for `--cache-dir` option of `pip install`, if set.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
@model_validator(mode="after")
|
|
44
|
+
def _validate_versions(self) -> Self:
|
|
45
|
+
if self.default_version not in self.versions.keys():
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"The default Python version ('{self.default_version}') is "
|
|
48
|
+
f"not available in {list(self.versions.keys())}."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return self
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from ..utils_pixi import simplify_pyproject_toml
|
|
4
|
+
from fractal_server.app.models import Resource
|
|
4
5
|
from fractal_server.app.schemas.v2 import TaskCreateV2
|
|
5
|
-
from fractal_server.config import get_settings
|
|
6
6
|
from fractal_server.logger import get_logger
|
|
7
7
|
from fractal_server.logger import set_logger
|
|
8
|
-
from fractal_server.syringe import Inject
|
|
9
8
|
from fractal_server.tasks.v2.utils_templates import customize_template
|
|
10
9
|
from fractal_server.utils import execute_command_sync
|
|
11
10
|
|
|
12
|
-
|
|
13
11
|
logger = set_logger(__name__)
|
|
14
12
|
|
|
15
13
|
|
|
@@ -78,7 +76,10 @@ def check_task_files_exist(task_list: list[TaskCreateV2]) -> None:
|
|
|
78
76
|
)
|
|
79
77
|
|
|
80
78
|
|
|
81
|
-
def edit_pyproject_toml_in_place_local(
|
|
79
|
+
def edit_pyproject_toml_in_place_local(
|
|
80
|
+
pyproject_toml_path: Path,
|
|
81
|
+
resource: Resource,
|
|
82
|
+
) -> None:
|
|
82
83
|
"""
|
|
83
84
|
Wrapper of `simplify_pyproject_toml`, with I/O.
|
|
84
85
|
"""
|
|
@@ -88,11 +89,10 @@ def edit_pyproject_toml_in_place_local(pyproject_toml_path: Path) -> None:
|
|
|
88
89
|
pyproject_contents = f.read()
|
|
89
90
|
|
|
90
91
|
# Simplify contents
|
|
91
|
-
settings = Inject(get_settings)
|
|
92
92
|
new_pyproject_contents = simplify_pyproject_toml(
|
|
93
93
|
original_toml_string=pyproject_contents,
|
|
94
|
-
pixi_environment=
|
|
95
|
-
pixi_platform=
|
|
94
|
+
pixi_environment=resource.tasks_pixi_config["DEFAULT_ENVIRONMENT"],
|
|
95
|
+
pixi_platform=resource.tasks_pixi_config["DEFAULT_PLATFORM"],
|
|
96
96
|
)
|
|
97
97
|
# Write new `pyproject.toml`
|
|
98
98
|
with pyproject_toml_path.open("w") as f:
|
|
@@ -7,6 +7,8 @@ from tempfile import TemporaryDirectory
|
|
|
7
7
|
from ..utils_database import create_db_tasks_and_update_task_group_sync
|
|
8
8
|
from ._utils import _customize_and_run_template
|
|
9
9
|
from fractal_server.app.db import get_sync_db
|
|
10
|
+
from fractal_server.app.models import Profile
|
|
11
|
+
from fractal_server.app.models import Resource
|
|
10
12
|
from fractal_server.app.models.v2 import TaskGroupV2
|
|
11
13
|
from fractal_server.app.schemas.v2 import FractalUploadedFile
|
|
12
14
|
from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
|
|
@@ -25,7 +27,7 @@ from fractal_server.tasks.v2.utils_background import get_current_log
|
|
|
25
27
|
from fractal_server.tasks.v2.utils_background import prepare_tasks_metadata
|
|
26
28
|
from fractal_server.tasks.v2.utils_package_names import compare_package_names
|
|
27
29
|
from fractal_server.tasks.v2.utils_python_interpreter import (
|
|
28
|
-
|
|
30
|
+
get_python_interpreter,
|
|
29
31
|
)
|
|
30
32
|
from fractal_server.tasks.v2.utils_templates import get_collection_replacements
|
|
31
33
|
from fractal_server.tasks.v2.utils_templates import (
|
|
@@ -39,6 +41,8 @@ def collect_local(
|
|
|
39
41
|
*,
|
|
40
42
|
task_group_activity_id: int,
|
|
41
43
|
task_group_id: int,
|
|
44
|
+
resource: Resource,
|
|
45
|
+
profile: Profile,
|
|
42
46
|
wheel_file: FractalUploadedFile | None = None,
|
|
43
47
|
) -> None:
|
|
44
48
|
"""
|
|
@@ -52,9 +56,10 @@ def collect_local(
|
|
|
52
56
|
https://github.com/encode/starlette/blob/master/starlette/background.py).
|
|
53
57
|
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
Args:
|
|
56
60
|
task_group_id:
|
|
57
61
|
task_group_activity_id:
|
|
62
|
+
resource: Resource
|
|
58
63
|
wheel_file:
|
|
59
64
|
"""
|
|
60
65
|
|
|
@@ -111,11 +116,14 @@ def collect_local(
|
|
|
111
116
|
task_group = add_commit_refresh(obj=task_group, db=db)
|
|
112
117
|
|
|
113
118
|
# Prepare replacements for templates
|
|
119
|
+
python_bin = get_python_interpreter(
|
|
120
|
+
python_version=task_group.python_version,
|
|
121
|
+
resource=resource,
|
|
122
|
+
)
|
|
114
123
|
replacements = get_collection_replacements(
|
|
115
124
|
task_group=task_group,
|
|
116
|
-
python_bin=
|
|
117
|
-
|
|
118
|
-
),
|
|
125
|
+
python_bin=python_bin,
|
|
126
|
+
resource=resource,
|
|
119
127
|
)
|
|
120
128
|
|
|
121
129
|
# Prepare common arguments for `_customize_and_run_template``
|