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
|
@@ -10,25 +10,25 @@ from fastapi import Request
|
|
|
10
10
|
from fastapi import status
|
|
11
11
|
from sqlmodel import select
|
|
12
12
|
|
|
13
|
+
from ...aux.validate_user_profile import validate_user_profile
|
|
13
14
|
from ._aux_functions import _get_dataset_check_owner
|
|
14
15
|
from ._aux_functions import _get_workflow_check_owner
|
|
15
16
|
from ._aux_functions import clean_app_job_list_v2
|
|
16
17
|
from ._aux_functions_tasks import _check_type_filters_compatibility
|
|
17
18
|
from fractal_server.app.db import AsyncSession
|
|
18
19
|
from fractal_server.app.db import get_async_db
|
|
20
|
+
from fractal_server.app.models import Profile
|
|
19
21
|
from fractal_server.app.models import TaskGroupV2
|
|
20
22
|
from fractal_server.app.models import UserOAuth
|
|
21
23
|
from fractal_server.app.models.v2 import JobV2
|
|
22
24
|
from fractal_server.app.routes.api.v2._aux_functions_tasks import (
|
|
23
25
|
_get_task_read_access,
|
|
24
26
|
)
|
|
25
|
-
from fractal_server.app.routes.auth import
|
|
26
|
-
from fractal_server.app.routes.aux.validate_user_settings import (
|
|
27
|
-
validate_user_settings,
|
|
28
|
-
)
|
|
27
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
29
28
|
from fractal_server.app.schemas.v2 import JobCreateV2
|
|
30
29
|
from fractal_server.app.schemas.v2 import JobReadV2
|
|
31
30
|
from fractal_server.app.schemas.v2 import JobStatusTypeV2
|
|
31
|
+
from fractal_server.app.schemas.v2 import ResourceType
|
|
32
32
|
from fractal_server.config import get_settings
|
|
33
33
|
from fractal_server.logger import set_logger
|
|
34
34
|
from fractal_server.runner.set_start_and_last_task_index import (
|
|
@@ -37,7 +37,7 @@ from fractal_server.runner.set_start_and_last_task_index import (
|
|
|
37
37
|
from fractal_server.runner.v2.submit_workflow import submit_workflow
|
|
38
38
|
from fractal_server.syringe import Inject
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
FRACTAL_CACHE_DIR = ".fractal_cache"
|
|
41
41
|
router = APIRouter()
|
|
42
42
|
logger = set_logger(__name__)
|
|
43
43
|
|
|
@@ -54,11 +54,14 @@ async def apply_workflow(
|
|
|
54
54
|
job_create: JobCreateV2,
|
|
55
55
|
background_tasks: BackgroundTasks,
|
|
56
56
|
request: Request,
|
|
57
|
-
user: UserOAuth = Depends(
|
|
57
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
58
58
|
db: AsyncSession = Depends(get_async_db),
|
|
59
59
|
) -> JobReadV2 | None:
|
|
60
60
|
# Remove non-submitted V2 jobs from the app state when the list grows
|
|
61
61
|
# beyond a threshold
|
|
62
|
+
# NOTE: this may lead to a race condition on `app.state.jobsV2` if two
|
|
63
|
+
# requests take place at the same time and `clean_app_job_list_v2` is
|
|
64
|
+
# somewhat slow.
|
|
62
65
|
settings = Inject(get_settings)
|
|
63
66
|
if (
|
|
64
67
|
len(request.app.state.jobsV2)
|
|
@@ -78,6 +81,17 @@ async def apply_workflow(
|
|
|
78
81
|
project = output["project"]
|
|
79
82
|
dataset = output["dataset"]
|
|
80
83
|
|
|
84
|
+
# Verify that user's resource matches with project resource
|
|
85
|
+
res = await db.execute(
|
|
86
|
+
select(Profile.resource_id).where(Profile.id == user.profile_id)
|
|
87
|
+
)
|
|
88
|
+
user_resource_id = res.scalar_one()
|
|
89
|
+
if project.resource_id != user_resource_id:
|
|
90
|
+
raise HTTPException(
|
|
91
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
92
|
+
detail="Project resource does not match with user's resource",
|
|
93
|
+
)
|
|
94
|
+
|
|
81
95
|
workflow = await _get_workflow_check_owner(
|
|
82
96
|
project_id=project_id, workflow_id=workflow_id, user_id=user.id, db=db
|
|
83
97
|
)
|
|
@@ -122,11 +136,12 @@ async def apply_workflow(
|
|
|
122
136
|
)
|
|
123
137
|
used_task_group_ids.add(task.taskgroupv2_id)
|
|
124
138
|
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
139
|
+
# Get validated resource and profile
|
|
140
|
+
resource, profile = await validate_user_profile(
|
|
141
|
+
user=user,
|
|
142
|
+
db=db,
|
|
129
143
|
)
|
|
144
|
+
|
|
130
145
|
# Check that no other job with the same dataset_id is SUBMITTED
|
|
131
146
|
stm = (
|
|
132
147
|
select(JobV2)
|
|
@@ -144,7 +159,7 @@ async def apply_workflow(
|
|
|
144
159
|
)
|
|
145
160
|
|
|
146
161
|
if job_create.slurm_account is not None:
|
|
147
|
-
if job_create.slurm_account not in
|
|
162
|
+
if job_create.slurm_account not in user.slurm_accounts:
|
|
148
163
|
raise HTTPException(
|
|
149
164
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
150
165
|
detail=(
|
|
@@ -153,15 +168,15 @@ async def apply_workflow(
|
|
|
153
168
|
),
|
|
154
169
|
)
|
|
155
170
|
else:
|
|
156
|
-
if len(
|
|
157
|
-
job_create.slurm_account =
|
|
171
|
+
if len(user.slurm_accounts) > 0:
|
|
172
|
+
job_create.slurm_account = user.slurm_accounts[0]
|
|
158
173
|
|
|
159
174
|
# User appropriate FractalSSH object
|
|
160
|
-
if
|
|
175
|
+
if resource.type == ResourceType.SLURM_SSH:
|
|
161
176
|
ssh_config = dict(
|
|
162
|
-
user=
|
|
163
|
-
host=
|
|
164
|
-
key_path=
|
|
177
|
+
user=profile.username,
|
|
178
|
+
host=resource.host,
|
|
179
|
+
key_path=profile.ssh_key_path,
|
|
165
180
|
)
|
|
166
181
|
fractal_ssh_list = request.app.state.fractal_ssh_list
|
|
167
182
|
try:
|
|
@@ -184,16 +199,14 @@ async def apply_workflow(
|
|
|
184
199
|
dataset_id=dataset_id,
|
|
185
200
|
workflow_id=workflow_id,
|
|
186
201
|
user_email=user.email,
|
|
187
|
-
# The 'filters' field is not supported any more but still exists as a
|
|
188
|
-
# database column, therefore we manually exclude it from dumps.
|
|
189
202
|
dataset_dump=json.loads(
|
|
190
|
-
dataset.model_dump_json(exclude={"images", "history"
|
|
203
|
+
dataset.model_dump_json(exclude={"images", "history"})
|
|
191
204
|
),
|
|
192
205
|
workflow_dump=json.loads(
|
|
193
206
|
workflow.model_dump_json(exclude={"task_list"})
|
|
194
207
|
),
|
|
195
208
|
project_dump=json.loads(
|
|
196
|
-
project.model_dump_json(exclude={"user_list"})
|
|
209
|
+
project.model_dump_json(exclude={"user_list", "resource_id"})
|
|
197
210
|
),
|
|
198
211
|
**job_create.model_dump(),
|
|
199
212
|
)
|
|
@@ -214,26 +227,23 @@ async def apply_workflow(
|
|
|
214
227
|
|
|
215
228
|
# Define server-side job directory
|
|
216
229
|
timestamp_string = job.start_timestamp.strftime("%Y%m%d_%H%M%S")
|
|
217
|
-
WORKFLOW_DIR_LOCAL =
|
|
230
|
+
WORKFLOW_DIR_LOCAL = Path(resource.jobs_local_dir) / (
|
|
218
231
|
f"proj_v2_{project_id:07d}_wf_{workflow_id:07d}_job_{job.id:07d}"
|
|
219
232
|
f"_{timestamp_string}"
|
|
220
233
|
)
|
|
221
234
|
|
|
222
|
-
cache_dir = (
|
|
223
|
-
Path(user_settings.project_dir) / ".fractal_cache"
|
|
224
|
-
if user_settings.project_dir is not None
|
|
225
|
-
else None
|
|
226
|
-
)
|
|
227
|
-
|
|
228
235
|
# Define user-side job directory
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
cache_dir = Path(user.project_dir, FRACTAL_CACHE_DIR)
|
|
237
|
+
match resource.type:
|
|
238
|
+
case ResourceType.LOCAL:
|
|
239
|
+
WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
|
|
240
|
+
case ResourceType.SLURM_SUDO:
|
|
241
|
+
WORKFLOW_DIR_REMOTE = cache_dir / WORKFLOW_DIR_LOCAL.name
|
|
242
|
+
case ResourceType.SLURM_SSH:
|
|
243
|
+
WORKFLOW_DIR_REMOTE = Path(
|
|
244
|
+
profile.jobs_remote_dir,
|
|
245
|
+
WORKFLOW_DIR_LOCAL.name,
|
|
246
|
+
)
|
|
237
247
|
|
|
238
248
|
# Update job folders in the db
|
|
239
249
|
job.working_dir = WORKFLOW_DIR_LOCAL.as_posix()
|
|
@@ -241,20 +251,17 @@ async def apply_workflow(
|
|
|
241
251
|
await db.merge(job)
|
|
242
252
|
await db.commit()
|
|
243
253
|
|
|
244
|
-
# Expunge user settings from db, to use in background task
|
|
245
|
-
db.expunge(user_settings)
|
|
246
|
-
|
|
247
254
|
background_tasks.add_task(
|
|
248
255
|
submit_workflow,
|
|
249
256
|
workflow_id=workflow.id,
|
|
250
257
|
dataset_id=dataset.id,
|
|
251
258
|
job_id=job.id,
|
|
252
259
|
user_id=user.id,
|
|
253
|
-
user_settings=user_settings,
|
|
254
260
|
worker_init=job.worker_init,
|
|
255
|
-
|
|
256
|
-
user_cache_dir=cache_dir.as_posix() if cache_dir else None,
|
|
261
|
+
user_cache_dir=cache_dir.as_posix(),
|
|
257
262
|
fractal_ssh=fractal_ssh,
|
|
263
|
+
resource=resource,
|
|
264
|
+
profile=profile,
|
|
258
265
|
)
|
|
259
266
|
request.app.state.jobsV2.append(job.id)
|
|
260
267
|
logger.info(
|
|
@@ -9,6 +9,8 @@ from sqlmodel import func
|
|
|
9
9
|
from sqlmodel import or_
|
|
10
10
|
from sqlmodel import select
|
|
11
11
|
|
|
12
|
+
from ...aux.validate_user_profile import validate_user_profile
|
|
13
|
+
from ._aux_functions import _get_user_resource_id
|
|
12
14
|
from ._aux_functions_tasks import _get_task_full_access
|
|
13
15
|
from ._aux_functions_tasks import _get_task_read_access
|
|
14
16
|
from ._aux_functions_tasks import _get_valid_user_group_id
|
|
@@ -20,8 +22,7 @@ from fractal_server.app.models import LinkUserGroup
|
|
|
20
22
|
from fractal_server.app.models import UserOAuth
|
|
21
23
|
from fractal_server.app.models.v2 import TaskGroupV2
|
|
22
24
|
from fractal_server.app.models.v2 import TaskV2
|
|
23
|
-
from fractal_server.app.routes.auth import
|
|
24
|
-
from fractal_server.app.routes.auth import current_active_verified_user
|
|
25
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
25
26
|
from fractal_server.app.schemas.v2 import TaskCreateV2
|
|
26
27
|
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
|
27
28
|
from fractal_server.app.schemas.v2 import TaskReadV2
|
|
@@ -40,16 +41,20 @@ async def get_list_task(
|
|
|
40
41
|
category: str | None = None,
|
|
41
42
|
modality: str | None = None,
|
|
42
43
|
author: str | None = None,
|
|
43
|
-
user: UserOAuth = Depends(
|
|
44
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
44
45
|
db: AsyncSession = Depends(get_async_db),
|
|
45
46
|
) -> list[TaskReadV2]:
|
|
46
47
|
"""
|
|
47
48
|
Get list of available tasks
|
|
48
49
|
"""
|
|
50
|
+
|
|
51
|
+
user_resource_id = await _get_user_resource_id(user_id=user.id, db=db)
|
|
52
|
+
|
|
49
53
|
stm = (
|
|
50
54
|
select(TaskV2)
|
|
51
55
|
.join(TaskGroupV2)
|
|
52
56
|
.where(TaskGroupV2.id == TaskV2.taskgroupv2_id)
|
|
57
|
+
.where(TaskGroupV2.resource_id == user_resource_id)
|
|
53
58
|
.where(
|
|
54
59
|
or_(
|
|
55
60
|
TaskGroupV2.user_id == user.id,
|
|
@@ -68,6 +73,7 @@ async def get_list_task(
|
|
|
68
73
|
if author is not None:
|
|
69
74
|
stm = stm.where(TaskV2.authors.icontains(author))
|
|
70
75
|
|
|
76
|
+
stm = stm.order_by(TaskV2.id)
|
|
71
77
|
res = await db.execute(stm)
|
|
72
78
|
task_list = list(res.scalars().all())
|
|
73
79
|
await db.close()
|
|
@@ -82,7 +88,7 @@ async def get_list_task(
|
|
|
82
88
|
@router.get("/{task_id}/", response_model=TaskReadV2)
|
|
83
89
|
async def get_task(
|
|
84
90
|
task_id: int,
|
|
85
|
-
user: UserOAuth = Depends(
|
|
91
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
86
92
|
db: AsyncSession = Depends(get_async_db),
|
|
87
93
|
) -> TaskReadV2:
|
|
88
94
|
"""
|
|
@@ -96,7 +102,7 @@ async def get_task(
|
|
|
96
102
|
async def patch_task(
|
|
97
103
|
task_id: int,
|
|
98
104
|
task_update: TaskUpdateV2,
|
|
99
|
-
user: UserOAuth = Depends(
|
|
105
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
100
106
|
db: AsyncSession = Depends(get_async_db),
|
|
101
107
|
) -> TaskReadV2 | None:
|
|
102
108
|
"""
|
|
@@ -137,13 +143,20 @@ async def create_task(
|
|
|
137
143
|
task: TaskCreateV2,
|
|
138
144
|
user_group_id: int | None = None,
|
|
139
145
|
private: bool = False,
|
|
140
|
-
user: UserOAuth = Depends(
|
|
146
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
141
147
|
db: AsyncSession = Depends(get_async_db),
|
|
142
148
|
) -> TaskReadV2 | None:
|
|
143
149
|
"""
|
|
144
150
|
Create a new task
|
|
145
151
|
"""
|
|
146
152
|
|
|
153
|
+
# Get validated resource and profile
|
|
154
|
+
resource, profile = await validate_user_profile(
|
|
155
|
+
user=user,
|
|
156
|
+
db=db,
|
|
157
|
+
)
|
|
158
|
+
resource_id = resource.id
|
|
159
|
+
|
|
147
160
|
# Validate query parameters related to user-group ownership
|
|
148
161
|
user_group_id = await _get_valid_user_group_id(
|
|
149
162
|
user_group_id=user_group_id,
|
|
@@ -179,7 +192,11 @@ async def create_task(
|
|
|
179
192
|
db_task = TaskV2(**task.model_dump(exclude_unset=True))
|
|
180
193
|
pkg_name = db_task.name
|
|
181
194
|
await _verify_non_duplication_user_constraint(
|
|
182
|
-
db=db,
|
|
195
|
+
db=db,
|
|
196
|
+
pkg_name=pkg_name,
|
|
197
|
+
user_id=user.id,
|
|
198
|
+
version=db_task.version,
|
|
199
|
+
user_resource_id=resource_id,
|
|
183
200
|
)
|
|
184
201
|
await _verify_non_duplication_group_constraint(
|
|
185
202
|
db=db,
|
|
@@ -190,6 +207,7 @@ async def create_task(
|
|
|
190
207
|
db_task_group = TaskGroupV2(
|
|
191
208
|
user_id=user.id,
|
|
192
209
|
user_group_id=user_group_id,
|
|
210
|
+
resource_id=resource_id,
|
|
193
211
|
active=True,
|
|
194
212
|
task_list=[db_task],
|
|
195
213
|
origin=TaskGroupV2OriginEnum.OTHER,
|
|
@@ -207,7 +225,7 @@ async def create_task(
|
|
|
207
225
|
@router.delete("/{task_id}/", status_code=204)
|
|
208
226
|
async def delete_task(
|
|
209
227
|
task_id: int,
|
|
210
|
-
user: UserOAuth = Depends(
|
|
228
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
211
229
|
db: AsyncSession = Depends(get_async_db),
|
|
212
230
|
) -> Response:
|
|
213
231
|
"""
|
|
@@ -14,10 +14,8 @@ from pydantic import BaseModel
|
|
|
14
14
|
from pydantic import model_validator
|
|
15
15
|
from pydantic import ValidationError
|
|
16
16
|
|
|
17
|
-
from .....config import get_settings
|
|
18
17
|
from .....logger import reset_logger_handlers
|
|
19
18
|
from .....logger import set_logger
|
|
20
|
-
from .....syringe import Inject
|
|
21
19
|
from ....db import AsyncSession
|
|
22
20
|
from ....db import get_async_db
|
|
23
21
|
from ....models.v2 import TaskGroupV2
|
|
@@ -26,7 +24,7 @@ from ....schemas.v2 import TaskCollectPipV2
|
|
|
26
24
|
from ....schemas.v2 import TaskGroupActivityStatusV2
|
|
27
25
|
from ....schemas.v2 import TaskGroupActivityV2Read
|
|
28
26
|
from ....schemas.v2 import TaskGroupCreateV2Strict
|
|
29
|
-
from ...aux.
|
|
27
|
+
from ...aux.validate_user_profile import validate_user_profile
|
|
30
28
|
from ._aux_functions_task_lifecycle import get_package_version_from_pypi
|
|
31
29
|
from ._aux_functions_tasks import _get_valid_user_group_id
|
|
32
30
|
from ._aux_functions_tasks import _verify_non_duplication_group_constraint
|
|
@@ -34,12 +32,12 @@ from ._aux_functions_tasks import _verify_non_duplication_group_path
|
|
|
34
32
|
from ._aux_functions_tasks import _verify_non_duplication_user_constraint
|
|
35
33
|
from fractal_server.app.models import UserOAuth
|
|
36
34
|
from fractal_server.app.models.v2 import TaskGroupActivityV2
|
|
37
|
-
from fractal_server.app.routes.auth import
|
|
35
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
36
|
+
from fractal_server.app.schemas.v2 import ResourceType
|
|
38
37
|
from fractal_server.app.schemas.v2 import (
|
|
39
38
|
TaskGroupActivityActionV2,
|
|
40
39
|
)
|
|
41
40
|
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
|
42
|
-
from fractal_server.ssh._fabric import SSHConfig
|
|
43
41
|
from fractal_server.tasks.v2.local.collect import (
|
|
44
42
|
collect_local,
|
|
45
43
|
)
|
|
@@ -47,7 +45,7 @@ from fractal_server.tasks.v2.ssh import collect_ssh
|
|
|
47
45
|
from fractal_server.tasks.v2.utils_package_names import _parse_wheel_filename
|
|
48
46
|
from fractal_server.tasks.v2.utils_package_names import normalize_package_name
|
|
49
47
|
from fractal_server.tasks.v2.utils_python_interpreter import (
|
|
50
|
-
|
|
48
|
+
get_python_interpreter,
|
|
51
49
|
)
|
|
52
50
|
|
|
53
51
|
|
|
@@ -162,14 +160,19 @@ async def collect_tasks_pip(
|
|
|
162
160
|
request_data: CollectionRequestData = Depends(parse_request_data),
|
|
163
161
|
private: bool = False,
|
|
164
162
|
user_group_id: int | None = None,
|
|
165
|
-
user: UserOAuth = Depends(
|
|
163
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
166
164
|
db: AsyncSession = Depends(get_async_db),
|
|
167
165
|
) -> TaskGroupActivityV2Read:
|
|
168
166
|
"""
|
|
169
167
|
Task-collection endpoint
|
|
170
168
|
"""
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
|
|
170
|
+
# Get validated resource and profile
|
|
171
|
+
resource, profile = await validate_user_profile(
|
|
172
|
+
user=user,
|
|
173
|
+
db=db,
|
|
174
|
+
)
|
|
175
|
+
resource_id = resource.id
|
|
173
176
|
|
|
174
177
|
# Get some validated request data
|
|
175
178
|
task_collect = request_data.task_collect
|
|
@@ -177,26 +180,28 @@ async def collect_tasks_pip(
|
|
|
177
180
|
# Initialize task-group attributes
|
|
178
181
|
task_group_attrs = dict(
|
|
179
182
|
user_id=user.id,
|
|
183
|
+
resource_id=resource_id,
|
|
180
184
|
origin=request_data.origin,
|
|
181
185
|
)
|
|
182
186
|
|
|
183
187
|
# Set/check python version
|
|
184
188
|
if task_collect.python_version is None:
|
|
185
|
-
task_group_attrs[
|
|
186
|
-
"
|
|
187
|
-
]
|
|
189
|
+
task_group_attrs["python_version"] = resource.tasks_python_config[
|
|
190
|
+
"default_version"
|
|
191
|
+
]
|
|
188
192
|
else:
|
|
189
193
|
task_group_attrs["python_version"] = task_collect.python_version
|
|
190
194
|
try:
|
|
191
|
-
|
|
192
|
-
python_version=task_group_attrs["python_version"]
|
|
195
|
+
get_python_interpreter(
|
|
196
|
+
python_version=task_group_attrs["python_version"],
|
|
197
|
+
resource=resource,
|
|
193
198
|
)
|
|
194
199
|
except ValueError:
|
|
195
200
|
raise HTTPException(
|
|
196
201
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
197
202
|
detail=(
|
|
198
|
-
f"Python version {task_group_attrs['python_version']}
|
|
199
|
-
"not available
|
|
203
|
+
f"Python version {task_group_attrs['python_version']} "
|
|
204
|
+
"is not available on this Fractal instance."
|
|
200
205
|
),
|
|
201
206
|
)
|
|
202
207
|
|
|
@@ -259,16 +264,11 @@ async def collect_tasks_pip(
|
|
|
259
264
|
# Set user_group_id
|
|
260
265
|
task_group_attrs["user_group_id"] = user_group_id
|
|
261
266
|
|
|
262
|
-
# Validate user settings (backend-specific)
|
|
263
|
-
user_settings = await validate_user_settings(
|
|
264
|
-
user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
|
|
265
|
-
)
|
|
266
|
-
|
|
267
267
|
# Set path and venv_path
|
|
268
|
-
if
|
|
269
|
-
base_tasks_path =
|
|
268
|
+
if resource.type == ResourceType.SLURM_SSH:
|
|
269
|
+
base_tasks_path = profile.tasks_remote_dir
|
|
270
270
|
else:
|
|
271
|
-
base_tasks_path =
|
|
271
|
+
base_tasks_path = resource.tasks_local_dir
|
|
272
272
|
task_group_path = (
|
|
273
273
|
Path(base_tasks_path)
|
|
274
274
|
/ str(user.id)
|
|
@@ -294,6 +294,7 @@ async def collect_tasks_pip(
|
|
|
294
294
|
user_id=user.id,
|
|
295
295
|
pkg_name=task_group_attrs["pkg_name"],
|
|
296
296
|
version=task_group_attrs["version"],
|
|
297
|
+
user_resource_id=resource_id,
|
|
297
298
|
db=db,
|
|
298
299
|
)
|
|
299
300
|
await _verify_non_duplication_group_constraint(
|
|
@@ -304,12 +305,13 @@ async def collect_tasks_pip(
|
|
|
304
305
|
)
|
|
305
306
|
await _verify_non_duplication_group_path(
|
|
306
307
|
path=task_group_attrs["path"],
|
|
308
|
+
resource_id=resource_id,
|
|
307
309
|
db=db,
|
|
308
310
|
)
|
|
309
311
|
|
|
310
312
|
# On-disk checks
|
|
311
313
|
|
|
312
|
-
if
|
|
314
|
+
if resource.type != ResourceType.SLURM_SSH:
|
|
313
315
|
# Verify that folder does not exist (for local collection)
|
|
314
316
|
if Path(task_group_path).exists():
|
|
315
317
|
raise HTTPException(
|
|
@@ -340,33 +342,20 @@ async def collect_tasks_pip(
|
|
|
340
342
|
|
|
341
343
|
# END of SSH/non-SSH common part
|
|
342
344
|
|
|
343
|
-
if
|
|
344
|
-
|
|
345
|
-
# Use appropriate FractalSSH object
|
|
346
|
-
ssh_config = SSHConfig(
|
|
347
|
-
user=user_settings.ssh_username,
|
|
348
|
-
host=user_settings.ssh_host,
|
|
349
|
-
key_path=user_settings.ssh_private_key_path,
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
background_tasks.add_task(
|
|
353
|
-
collect_ssh,
|
|
354
|
-
task_group_id=task_group.id,
|
|
355
|
-
task_group_activity_id=task_group_activity.id,
|
|
356
|
-
ssh_config=ssh_config,
|
|
357
|
-
tasks_base_dir=user_settings.ssh_tasks_dir,
|
|
358
|
-
wheel_file=wheel_file,
|
|
359
|
-
)
|
|
360
|
-
|
|
345
|
+
if resource.type == ResourceType.SLURM_SSH:
|
|
346
|
+
collect_function = collect_ssh
|
|
361
347
|
else:
|
|
362
|
-
|
|
348
|
+
collect_function = collect_local
|
|
349
|
+
|
|
350
|
+
background_tasks.add_task(
|
|
351
|
+
collect_function,
|
|
352
|
+
task_group_id=task_group.id,
|
|
353
|
+
task_group_activity_id=task_group_activity.id,
|
|
354
|
+
wheel_file=wheel_file,
|
|
355
|
+
resource=resource,
|
|
356
|
+
profile=profile,
|
|
357
|
+
)
|
|
363
358
|
|
|
364
|
-
background_tasks.add_task(
|
|
365
|
-
collect_local,
|
|
366
|
-
task_group_id=task_group.id,
|
|
367
|
-
task_group_activity_id=task_group_activity.id,
|
|
368
|
-
wheel_file=wheel_file,
|
|
369
|
-
)
|
|
370
359
|
logger.debug(
|
|
371
360
|
"Task-collection endpoint: start background collection "
|
|
372
361
|
"and return task_group_activity"
|
|
@@ -9,22 +9,22 @@ from fastapi import HTTPException
|
|
|
9
9
|
from fastapi import status
|
|
10
10
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
11
11
|
|
|
12
|
+
from ...aux.validate_user_profile import validate_user_profile
|
|
12
13
|
from ._aux_functions_tasks import _get_valid_user_group_id
|
|
13
14
|
from ._aux_functions_tasks import _verify_non_duplication_group_constraint
|
|
14
15
|
from ._aux_functions_tasks import _verify_non_duplication_user_constraint
|
|
15
16
|
from fractal_server.app.db import get_async_db
|
|
16
17
|
from fractal_server.app.models import UserOAuth
|
|
17
18
|
from fractal_server.app.models.v2 import TaskGroupV2
|
|
18
|
-
from fractal_server.app.routes.auth import
|
|
19
|
+
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
20
|
+
from fractal_server.app.schemas.v2 import ResourceType
|
|
19
21
|
from fractal_server.app.schemas.v2 import TaskCollectCustomV2
|
|
20
22
|
from fractal_server.app.schemas.v2 import TaskCreateV2
|
|
21
23
|
from fractal_server.app.schemas.v2 import TaskGroupCreateV2
|
|
22
24
|
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
|
23
25
|
from fractal_server.app.schemas.v2 import TaskReadV2
|
|
24
|
-
from fractal_server.config import get_settings
|
|
25
26
|
from fractal_server.logger import set_logger
|
|
26
27
|
from fractal_server.string_tools import validate_cmd
|
|
27
|
-
from fractal_server.syringe import Inject
|
|
28
28
|
from fractal_server.tasks.v2.utils_background import (
|
|
29
29
|
prepare_tasks_metadata,
|
|
30
30
|
)
|
|
@@ -44,10 +44,12 @@ async def collect_task_custom(
|
|
|
44
44
|
task_collect: TaskCollectCustomV2,
|
|
45
45
|
private: bool = False,
|
|
46
46
|
user_group_id: int | None = None,
|
|
47
|
-
user: UserOAuth = Depends(
|
|
47
|
+
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
48
48
|
db: AsyncSession = Depends(get_async_db),
|
|
49
49
|
) -> list[TaskReadV2]:
|
|
50
|
-
|
|
50
|
+
# Get validated resource and profile
|
|
51
|
+
resource, profile = await validate_user_profile(user=user, db=db)
|
|
52
|
+
resource_id = resource.id
|
|
51
53
|
|
|
52
54
|
# Validate query parameters related to user-group ownership
|
|
53
55
|
user_group_id = await _get_valid_user_group_id(
|
|
@@ -57,7 +59,7 @@ async def collect_task_custom(
|
|
|
57
59
|
db=db,
|
|
58
60
|
)
|
|
59
61
|
|
|
60
|
-
if
|
|
62
|
+
if resource.type == ResourceType.SLURM_SSH:
|
|
61
63
|
if task_collect.package_root is None:
|
|
62
64
|
raise HTTPException(
|
|
63
65
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
@@ -150,6 +152,7 @@ async def collect_task_custom(
|
|
|
150
152
|
user_id=user.id,
|
|
151
153
|
user_group_id=user_group_id,
|
|
152
154
|
version=task_collect.version,
|
|
155
|
+
resource_id=resource_id,
|
|
153
156
|
)
|
|
154
157
|
TaskGroupCreateV2(**task_group_attrs)
|
|
155
158
|
|
|
@@ -158,6 +161,7 @@ async def collect_task_custom(
|
|
|
158
161
|
user_id=user.id,
|
|
159
162
|
pkg_name=task_group_attrs["pkg_name"],
|
|
160
163
|
version=task_group_attrs["version"],
|
|
164
|
+
user_resource_id=resource_id,
|
|
161
165
|
db=db,
|
|
162
166
|
)
|
|
163
167
|
await _verify_non_duplication_group_constraint(
|