fractal-server 2.17.1a0__py3-none-any.whl → 2.17.2__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 +19 -18
- fractal_server/app/db/__init__.py +3 -3
- fractal_server/app/models/__init__.py +1 -1
- fractal_server/app/models/linkuserproject.py +3 -1
- fractal_server/app/models/security.py +22 -17
- fractal_server/app/models/v2/__init__.py +3 -1
- fractal_server/app/models/v2/accounting.py +9 -1
- fractal_server/app/models/v2/dataset.py +5 -1
- fractal_server/app/models/v2/history.py +15 -1
- fractal_server/app/models/v2/job.py +4 -0
- fractal_server/app/models/v2/profile.py +29 -0
- fractal_server/app/models/v2/project.py +5 -14
- fractal_server/app/models/v2/resource.py +4 -0
- fractal_server/app/models/v2/task_group.py +5 -7
- fractal_server/app/models/v2/workflow.py +2 -1
- fractal_server/app/routes/admin/v2/__init__.py +1 -2
- fractal_server/app/routes/admin/v2/accounting.py +1 -1
- fractal_server/app/routes/admin/v2/job.py +9 -9
- fractal_server/app/routes/admin/v2/profile.py +3 -2
- fractal_server/app/routes/admin/v2/resource.py +5 -5
- fractal_server/app/routes/admin/v2/task.py +28 -18
- fractal_server/app/routes/admin/v2/task_group.py +0 -1
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +1 -2
- fractal_server/app/routes/api/__init__.py +1 -0
- fractal_server/app/routes/api/v2/__init__.py +5 -6
- fractal_server/app/routes/api/v2/_aux_functions.py +70 -63
- fractal_server/app/routes/api/v2/_aux_functions_history.py +43 -20
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +2 -4
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +5 -7
- fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +1 -2
- fractal_server/app/routes/api/v2/dataset.py +13 -32
- fractal_server/app/routes/api/v2/history.py +35 -21
- fractal_server/app/routes/api/v2/images.py +3 -2
- fractal_server/app/routes/api/v2/job.py +17 -14
- fractal_server/app/routes/api/v2/pre_submission_checks.py +5 -4
- fractal_server/app/routes/api/v2/project.py +22 -17
- fractal_server/app/routes/api/v2/status_legacy.py +12 -11
- fractal_server/app/routes/api/v2/submit.py +11 -12
- fractal_server/app/routes/api/v2/task.py +4 -3
- fractal_server/app/routes/api/v2/task_collection.py +28 -30
- fractal_server/app/routes/api/v2/task_collection_custom.py +8 -7
- fractal_server/app/routes/api/v2/task_collection_pixi.py +1 -2
- fractal_server/app/routes/api/v2/task_group.py +7 -6
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +6 -6
- fractal_server/app/routes/api/v2/task_version_update.py +13 -12
- fractal_server/app/routes/api/v2/workflow.py +14 -31
- fractal_server/app/routes/api/v2/workflow_import.py +17 -19
- fractal_server/app/routes/api/v2/workflowtask.py +10 -12
- fractal_server/app/routes/auth/__init__.py +1 -3
- fractal_server/app/routes/auth/_aux_auth.py +1 -2
- fractal_server/app/routes/auth/current_user.py +4 -5
- fractal_server/app/routes/auth/group.py +7 -5
- fractal_server/app/routes/auth/login.py +1 -0
- fractal_server/app/routes/auth/oauth.py +4 -3
- fractal_server/app/routes/auth/register.py +4 -2
- fractal_server/app/routes/auth/users.py +10 -10
- fractal_server/app/routes/aux/_job.py +1 -1
- fractal_server/app/routes/aux/_runner.py +2 -2
- fractal_server/app/routes/pagination.py +1 -1
- fractal_server/app/schemas/user.py +3 -3
- fractal_server/app/schemas/v2/accounting.py +11 -0
- fractal_server/app/schemas/v2/dataset.py +28 -4
- fractal_server/app/schemas/v2/dumps.py +1 -0
- fractal_server/app/schemas/v2/manifest.py +4 -3
- fractal_server/app/schemas/v2/profile.py +53 -2
- fractal_server/app/schemas/v2/resource.py +109 -13
- fractal_server/app/schemas/v2/task.py +0 -1
- fractal_server/app/schemas/v2/task_collection.py +1 -1
- fractal_server/app/schemas/v2/workflowtask.py +4 -3
- fractal_server/app/security/__init__.py +4 -7
- fractal_server/app/security/signup_email.py +4 -5
- fractal_server/app/shutdown.py +23 -19
- fractal_server/config/_data.py +36 -25
- fractal_server/config/_database.py +19 -20
- fractal_server/config/_email.py +30 -38
- fractal_server/config/_main.py +34 -53
- fractal_server/config/_oauth.py +17 -21
- fractal_server/exceptions.py +4 -0
- fractal_server/images/models.py +3 -3
- fractal_server/images/status_tools.py +4 -2
- fractal_server/logger.py +1 -1
- fractal_server/main.py +4 -3
- fractal_server/migrations/versions/034a469ec2eb_task_groups.py +4 -8
- fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +1 -1
- fractal_server/migrations/versions/0f5f85bb2ae7_add_pre_pinned_packages.py +1 -0
- fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +1 -1
- fractal_server/migrations/versions/1a83a5260664_rename.py +1 -1
- fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +1 -0
- fractal_server/migrations/versions/316140ff7ee1_remove_usersettings_cache_dir.py +1 -1
- fractal_server/migrations/versions/40d6d6511b20_add_index_to_history_models.py +47 -0
- fractal_server/migrations/versions/45fbb391d7af_make_resource_id_fk_non_nullable.py +46 -0
- fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +1 -0
- fractal_server/migrations/versions/49d0856e9569_drop_table.py +62 -0
- fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +1 -1
- fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +1 -1
- fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +2 -1
- fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +7 -19
- fractal_server/migrations/versions/5bf02391cfef_v2.py +4 -10
- fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +1 -0
- fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +1 -1
- fractal_server/migrations/versions/7673fe18c05d_remove_project_dir_server_default.py +29 -0
- fractal_server/migrations/versions/791ce783d3d8_add_indices.py +1 -1
- fractal_server/migrations/versions/83bc2ad3ffcc_2_17_0.py +1 -0
- fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +1 -0
- fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +2 -4
- fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +1 -1
- fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +1 -0
- fractal_server/migrations/versions/969d84257cac_add_historyrun_task_id.py +1 -1
- fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +1 -1
- fractal_server/migrations/versions/981d588fe248_add_executor_error_log.py +1 -1
- fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +2 -4
- fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +1 -1
- fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +1 -1
- fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +1 -1
- fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +1 -1
- fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +1 -0
- fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +1 -0
- fractal_server/migrations/versions/b1e7f7a1ff71_task_group_for_pixi.py +1 -1
- fractal_server/migrations/versions/b3ffb095f973_json_to_jsonb.py +1 -0
- fractal_server/migrations/versions/c90a7c76e996_job_id_in_history_run.py +1 -1
- fractal_server/migrations/versions/caba9fb1ea5e_drop_useroauth_user_settings_id.py +49 -0
- fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +4 -9
- fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +1 -0
- fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +1 -1
- fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +1 -0
- fractal_server/migrations/versions/e0e717ae2f26_delete_linkuserproject_ondelete_project.py +50 -0
- fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +1 -0
- fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +1 -1
- fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +1 -0
- fractal_server/migrations/versions/f37aceb45062_make_historyunit_logfile_required.py +1 -1
- fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +1 -0
- fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +4 -9
- fractal_server/runner/config/_local.py +8 -5
- fractal_server/runner/config/_slurm.py +37 -33
- fractal_server/runner/config/slurm_mem_to_MB.py +0 -1
- fractal_server/runner/executors/base_runner.py +29 -4
- fractal_server/runner/executors/local/get_local_config.py +1 -0
- fractal_server/runner/executors/local/runner.py +14 -13
- fractal_server/runner/executors/slurm_common/_batching.py +5 -10
- fractal_server/runner/executors/slurm_common/base_slurm_runner.py +53 -27
- fractal_server/runner/executors/slurm_common/get_slurm_config.py +14 -7
- fractal_server/runner/executors/slurm_common/remote.py +3 -1
- fractal_server/runner/executors/slurm_common/slurm_config.py +1 -0
- fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -3
- fractal_server/runner/executors/slurm_ssh/runner.py +16 -11
- fractal_server/runner/executors/slurm_ssh/tar_commands.py +1 -0
- fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +1 -0
- fractal_server/runner/executors/slurm_sudo/runner.py +16 -11
- fractal_server/runner/task_files.py +9 -3
- fractal_server/runner/v2/_local.py +9 -4
- fractal_server/runner/v2/_slurm_ssh.py +11 -5
- fractal_server/runner/v2/_slurm_sudo.py +11 -5
- fractal_server/runner/v2/db_tools.py +0 -1
- fractal_server/runner/v2/deduplicate_list.py +2 -1
- fractal_server/runner/v2/runner.py +11 -14
- fractal_server/runner/v2/runner_functions.py +11 -14
- fractal_server/runner/v2/submit_workflow.py +7 -6
- fractal_server/ssh/_fabric.py +6 -13
- fractal_server/string_tools.py +0 -1
- fractal_server/syringe.py +1 -1
- fractal_server/tasks/config/_pixi.py +1 -1
- fractal_server/tasks/config/_python.py +16 -9
- fractal_server/tasks/utils.py +0 -1
- fractal_server/tasks/v2/local/_utils.py +1 -1
- fractal_server/tasks/v2/local/collect.py +10 -12
- fractal_server/tasks/v2/local/collect_pixi.py +9 -10
- fractal_server/tasks/v2/local/deactivate.py +7 -8
- fractal_server/tasks/v2/local/deactivate_pixi.py +4 -4
- fractal_server/tasks/v2/local/delete.py +1 -3
- fractal_server/tasks/v2/local/reactivate.py +7 -7
- fractal_server/tasks/v2/local/reactivate_pixi.py +7 -7
- fractal_server/tasks/v2/ssh/_utils.py +3 -3
- fractal_server/tasks/v2/ssh/collect.py +14 -19
- fractal_server/tasks/v2/ssh/collect_pixi.py +17 -19
- fractal_server/tasks/v2/ssh/deactivate.py +10 -8
- fractal_server/tasks/v2/ssh/deactivate_pixi.py +6 -5
- fractal_server/tasks/v2/ssh/delete.py +7 -5
- fractal_server/tasks/v2/ssh/reactivate.py +11 -11
- fractal_server/tasks/v2/ssh/reactivate_pixi.py +8 -9
- fractal_server/tasks/v2/templates/1_create_venv.sh +2 -0
- fractal_server/tasks/v2/templates/2_pip_install.sh +2 -0
- fractal_server/tasks/v2/templates/3_pip_freeze.sh +2 -0
- fractal_server/tasks/v2/templates/4_pip_show.sh +2 -0
- fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +3 -1
- fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +2 -0
- fractal_server/tasks/v2/templates/pixi_1_extract.sh +2 -0
- fractal_server/tasks/v2/templates/pixi_2_install.sh +2 -0
- fractal_server/tasks/v2/templates/pixi_3_post_install.sh +2 -0
- fractal_server/tasks/v2/utils_background.py +3 -3
- fractal_server/tasks/v2/utils_package_names.py +1 -2
- fractal_server/tasks/v2/utils_pixi.py +1 -3
- fractal_server/types/__init__.py +76 -1
- fractal_server/types/validators/_common_validators.py +1 -3
- fractal_server/types/validators/_workflow_task_arguments_validators.py +1 -2
- fractal_server/utils.py +1 -0
- fractal_server/zip_tools.py +34 -0
- {fractal_server-2.17.1a0.dist-info → fractal_server-2.17.2.dist-info}/METADATA +1 -1
- fractal_server-2.17.2.dist-info/RECORD +265 -0
- fractal_server/app/models/user_settings.py +0 -37
- fractal_server/app/routes/admin/v2/project.py +0 -41
- fractal_server/data_migrations/2_17_0.py +0 -339
- fractal_server-2.17.1a0.dist-info/RECORD +0 -262
- {fractal_server-2.17.1a0.dist-info → fractal_server-2.17.2.dist-info}/WHEEL +0 -0
- {fractal_server-2.17.1a0.dist-info → fractal_server-2.17.2.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.17.1a0.dist-info → fractal_server-2.17.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,15 +8,16 @@ from sqlmodel import select
|
|
|
8
8
|
|
|
9
9
|
from fractal_server.app.db import AsyncSession
|
|
10
10
|
from fractal_server.app.db import get_async_db
|
|
11
|
+
from fractal_server.app.models import LinkUserProjectV2
|
|
11
12
|
from fractal_server.app.models import TaskGroupV2
|
|
12
13
|
from fractal_server.app.models import UserOAuth
|
|
13
14
|
from fractal_server.app.models.v2 import TaskV2
|
|
14
15
|
from fractal_server.app.models.v2 import WorkflowTaskV2
|
|
15
16
|
from fractal_server.app.models.v2 import WorkflowV2
|
|
16
17
|
from fractal_server.app.routes.auth import current_superuser_act
|
|
17
|
-
from fractal_server.app.routes.pagination import get_pagination_params
|
|
18
18
|
from fractal_server.app.routes.pagination import PaginationRequest
|
|
19
19
|
from fractal_server.app.routes.pagination import PaginationResponse
|
|
20
|
+
from fractal_server.app.routes.pagination import get_pagination_params
|
|
20
21
|
from fractal_server.app.schemas.v2.task import TaskType
|
|
21
22
|
|
|
22
23
|
router = APIRouter()
|
|
@@ -106,16 +107,12 @@ async def query_tasks(
|
|
|
106
107
|
stm = stm.where(TaskV2.authors.icontains(author))
|
|
107
108
|
stm_count = stm_count.where(TaskV2.authors.icontains(author))
|
|
108
109
|
if resource_id is not None:
|
|
109
|
-
stm = (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
stm_count.join(TaskGroupV2)
|
|
116
|
-
.where(TaskGroupV2.id == TaskV2.taskgroupv2_id)
|
|
117
|
-
.where(TaskGroupV2.resource_id == resource_id)
|
|
118
|
-
)
|
|
110
|
+
stm = stm.join(
|
|
111
|
+
TaskGroupV2, TaskGroupV2.id == TaskV2.taskgroupv2_id
|
|
112
|
+
).where(TaskGroupV2.resource_id == resource_id)
|
|
113
|
+
stm_count = stm_count.join(
|
|
114
|
+
TaskGroupV2, TaskGroupV2.id == TaskV2.taskgroupv2_id
|
|
115
|
+
).where(TaskGroupV2.resource_id == resource_id)
|
|
119
116
|
|
|
120
117
|
# Find total number of elements
|
|
121
118
|
res_total_count = await db.execute(stm_count)
|
|
@@ -133,13 +130,30 @@ async def query_tasks(
|
|
|
133
130
|
for task in task_list:
|
|
134
131
|
stm = (
|
|
135
132
|
select(WorkflowV2)
|
|
136
|
-
.join(
|
|
137
|
-
|
|
133
|
+
.join(
|
|
134
|
+
WorkflowTaskV2,
|
|
135
|
+
WorkflowTaskV2.workflow_id == WorkflowV2.id,
|
|
136
|
+
)
|
|
138
137
|
.where(WorkflowTaskV2.task_id == task.id)
|
|
139
138
|
)
|
|
140
139
|
res = await db.execute(stm)
|
|
141
140
|
wf_list = res.scalars().all()
|
|
142
141
|
|
|
142
|
+
project_users = {}
|
|
143
|
+
for project_id in set([workflow.project_id for workflow in wf_list]):
|
|
144
|
+
res = await db.execute(
|
|
145
|
+
select(UserOAuth.id, UserOAuth.email)
|
|
146
|
+
.join(
|
|
147
|
+
LinkUserProjectV2,
|
|
148
|
+
LinkUserProjectV2.user_id == UserOAuth.id,
|
|
149
|
+
)
|
|
150
|
+
.where(LinkUserProjectV2.project_id == project_id)
|
|
151
|
+
)
|
|
152
|
+
project_users[project_id] = [
|
|
153
|
+
ProjectUser(id=p_user[0], email=p_user[1])
|
|
154
|
+
for p_user in res.all()
|
|
155
|
+
]
|
|
156
|
+
|
|
143
157
|
task_info_list.append(
|
|
144
158
|
dict(
|
|
145
159
|
task=task.model_dump(),
|
|
@@ -149,16 +163,12 @@ async def query_tasks(
|
|
|
149
163
|
workflow_name=workflow.name,
|
|
150
164
|
project_id=workflow.project.id,
|
|
151
165
|
project_name=workflow.project.name,
|
|
152
|
-
project_users=[
|
|
153
|
-
dict(id=user.id, email=user.email)
|
|
154
|
-
for user in workflow.project.user_list
|
|
155
|
-
],
|
|
166
|
+
project_users=project_users[workflow.project_id],
|
|
156
167
|
)
|
|
157
168
|
for workflow in wf_list
|
|
158
169
|
],
|
|
159
170
|
)
|
|
160
171
|
)
|
|
161
|
-
|
|
162
172
|
return PaginationResponse[TaskV2Info](
|
|
163
173
|
total_count=total_count,
|
|
164
174
|
page_size=page_size,
|
|
@@ -199,8 +199,7 @@ async def reactivate_task_group(
|
|
|
199
199
|
raise HTTPException(
|
|
200
200
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
201
201
|
detail=(
|
|
202
|
-
"Cannot reactivate a task group with "
|
|
203
|
-
f"{task_group.env_info=}."
|
|
202
|
+
f"Cannot reactivate a task group with {task_group.env_info=}."
|
|
204
203
|
),
|
|
205
204
|
)
|
|
206
205
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
2
|
`api/v2` module
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from fastapi import APIRouter
|
|
5
6
|
|
|
7
|
+
from fractal_server.config import get_settings
|
|
8
|
+
from fractal_server.syringe import Inject
|
|
9
|
+
|
|
6
10
|
from .dataset import router as dataset_router_v2
|
|
7
11
|
from .history import router as history_router_v2
|
|
8
12
|
from .images import router as images_routes_v2
|
|
@@ -21,9 +25,6 @@ from .task_version_update import router as task_version_update_router_v2
|
|
|
21
25
|
from .workflow import router as workflow_router_v2
|
|
22
26
|
from .workflow_import import router as workflow_import_router_v2
|
|
23
27
|
from .workflowtask import router as workflowtask_router_v2
|
|
24
|
-
from fractal_server.config import get_settings
|
|
25
|
-
from fractal_server.syringe import Inject
|
|
26
|
-
|
|
27
28
|
|
|
28
29
|
router_api_v2 = APIRouter()
|
|
29
30
|
|
|
@@ -34,9 +35,7 @@ router_api_v2.include_router(images_routes_v2, tags=["V2 Images"])
|
|
|
34
35
|
router_api_v2.include_router(project_router_v2, tags=["V2 Project"])
|
|
35
36
|
router_api_v2.include_router(submit_job_router_v2, tags=["V2 Job"])
|
|
36
37
|
router_api_v2.include_router(history_router_v2, tags=["V2 History"])
|
|
37
|
-
router_api_v2.include_router(
|
|
38
|
-
status_legacy_router_v2, tags=["V2 Status Legacy"]
|
|
39
|
-
)
|
|
38
|
+
router_api_v2.include_router(status_legacy_router_v2, tags=["V2 Status Legacy"])
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
settings = Inject(get_settings)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Auxiliary functions to get object from the database or perform simple checks
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from typing import Any
|
|
5
6
|
from typing import Literal
|
|
6
7
|
|
|
@@ -11,18 +12,18 @@ from sqlalchemy.orm.attributes import flag_modified
|
|
|
11
12
|
from sqlmodel import select
|
|
12
13
|
from sqlmodel.sql.expression import SelectOfScalar
|
|
13
14
|
|
|
14
|
-
from ....models.v2 import DatasetV2
|
|
15
|
-
from ....models.v2 import JobV2
|
|
16
|
-
from ....models.v2 import LinkUserProjectV2
|
|
17
|
-
from ....models.v2 import ProjectV2
|
|
18
|
-
from ....models.v2 import TaskV2
|
|
19
|
-
from ....models.v2 import WorkflowTaskV2
|
|
20
|
-
from ....models.v2 import WorkflowV2
|
|
21
|
-
from ....schemas.v2 import JobStatusTypeV2
|
|
22
15
|
from fractal_server.app.db import AsyncSession
|
|
23
16
|
from fractal_server.app.models import Profile
|
|
24
17
|
from fractal_server.app.models import Resource
|
|
25
18
|
from fractal_server.app.models import UserOAuth
|
|
19
|
+
from fractal_server.app.models.v2 import DatasetV2
|
|
20
|
+
from fractal_server.app.models.v2 import JobV2
|
|
21
|
+
from fractal_server.app.models.v2 import LinkUserProjectV2
|
|
22
|
+
from fractal_server.app.models.v2 import ProjectV2
|
|
23
|
+
from fractal_server.app.models.v2 import TaskV2
|
|
24
|
+
from fractal_server.app.models.v2 import WorkflowTaskV2
|
|
25
|
+
from fractal_server.app.models.v2 import WorkflowV2
|
|
26
|
+
from fractal_server.app.schemas.v2 import JobStatusTypeV2
|
|
26
27
|
from fractal_server.logger import set_logger
|
|
27
28
|
|
|
28
29
|
logger = set_logger(__name__)
|
|
@@ -88,29 +89,29 @@ async def _get_workflow_check_owner(
|
|
|
88
89
|
|
|
89
90
|
Raises:
|
|
90
91
|
HTTPException(status_code=404_NOT_FOUND):
|
|
91
|
-
If the workflow
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
If the project or the workflow do not exist or if they are not
|
|
93
|
+
associated
|
|
94
|
+
HTTPException(status_code=403_FORBIDDEN):
|
|
95
|
+
If the user is not a member of the project
|
|
94
96
|
"""
|
|
95
97
|
|
|
96
98
|
# Access control for project
|
|
97
|
-
|
|
99
|
+
await _get_project_check_owner(
|
|
98
100
|
project_id=project_id, user_id=user_id, db=db
|
|
99
101
|
)
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
res = await db.execute(
|
|
104
|
+
select(WorkflowV2)
|
|
105
|
+
.where(WorkflowV2.id == workflow_id)
|
|
106
|
+
.where(WorkflowV2.project_id == project_id)
|
|
107
|
+
.execution_options(populate_existing=True) # See issue 1087
|
|
108
|
+
)
|
|
109
|
+
workflow = res.scalars().one_or_none()
|
|
104
110
|
|
|
105
111
|
if not workflow:
|
|
106
112
|
raise HTTPException(
|
|
107
113
|
status_code=status.HTTP_404_NOT_FOUND, detail="Workflow not found"
|
|
108
114
|
)
|
|
109
|
-
if workflow.project_id != project.id:
|
|
110
|
-
raise HTTPException(
|
|
111
|
-
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
112
|
-
detail=(f"Invalid {project_id=} for {workflow_id=}."),
|
|
113
|
-
)
|
|
114
115
|
|
|
115
116
|
return workflow
|
|
116
117
|
|
|
@@ -138,9 +139,10 @@ async def _get_workflow_task_check_owner(
|
|
|
138
139
|
|
|
139
140
|
Raises:
|
|
140
141
|
HTTPException(status_code=404_NOT_FOUND):
|
|
141
|
-
If the
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
If the project, the workflow or the workflowtask do not exist or
|
|
143
|
+
if they are not associated
|
|
144
|
+
HTTPException(status_code=403_FORBIDDEN):
|
|
145
|
+
If the user is not a member of the project
|
|
144
146
|
"""
|
|
145
147
|
|
|
146
148
|
# Access control for workflow
|
|
@@ -151,21 +153,19 @@ async def _get_workflow_task_check_owner(
|
|
|
151
153
|
db=db,
|
|
152
154
|
)
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
res = await db.execute(
|
|
157
|
+
select(WorkflowTaskV2)
|
|
158
|
+
.where(WorkflowTaskV2.id == workflow_task_id)
|
|
159
|
+
.where(WorkflowTaskV2.workflow_id == workflow_id)
|
|
160
|
+
)
|
|
161
|
+
workflow_task = res.scalars().one_or_none()
|
|
162
|
+
|
|
163
|
+
if workflow_task is None:
|
|
157
164
|
raise HTTPException(
|
|
158
165
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
159
166
|
detail="WorkflowTask not found",
|
|
160
167
|
)
|
|
161
168
|
|
|
162
|
-
# If WorkflowTask is not part of the expected Workflow, exit
|
|
163
|
-
if workflow_id != workflow_task.workflow_id:
|
|
164
|
-
raise HTTPException(
|
|
165
|
-
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
166
|
-
detail=f"Invalid {workflow_id=} for {workflow_task_id=}",
|
|
167
|
-
)
|
|
168
|
-
|
|
169
169
|
return workflow_task, workflow
|
|
170
170
|
|
|
171
171
|
|
|
@@ -220,7 +220,7 @@ async def _check_project_exists(
|
|
|
220
220
|
"""
|
|
221
221
|
stm = (
|
|
222
222
|
select(ProjectV2)
|
|
223
|
-
.join(LinkUserProjectV2)
|
|
223
|
+
.join(LinkUserProjectV2, LinkUserProjectV2.project_id == ProjectV2.id)
|
|
224
224
|
.where(ProjectV2.name == project_name)
|
|
225
225
|
.where(LinkUserProjectV2.user_id == user_id)
|
|
226
226
|
)
|
|
@@ -253,27 +253,29 @@ async def _get_dataset_check_owner(
|
|
|
253
253
|
`project`).
|
|
254
254
|
|
|
255
255
|
Raises:
|
|
256
|
-
HTTPException(status_code=
|
|
257
|
-
If the dataset
|
|
256
|
+
HTTPException(status_code=404_UNPROCESSABLE_ENTITY):
|
|
257
|
+
If the project or the dataset do not exist or if they are not
|
|
258
|
+
associated
|
|
259
|
+
HTTPException(status_code=403_FORBIDDEN):
|
|
260
|
+
If the user is not a member of the project
|
|
258
261
|
"""
|
|
259
262
|
# Access control for project
|
|
260
263
|
project = await _get_project_check_owner(
|
|
261
264
|
project_id=project_id, user_id=user_id, db=db
|
|
262
265
|
)
|
|
263
266
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
res = await db.execute(
|
|
268
|
+
select(DatasetV2)
|
|
269
|
+
.where(DatasetV2.id == dataset_id)
|
|
270
|
+
.where(DatasetV2.project_id == project_id)
|
|
271
|
+
.execution_options(populate_existing=True) # See issue 1087
|
|
272
|
+
)
|
|
273
|
+
dataset = res.scalars().one_or_none()
|
|
267
274
|
|
|
268
|
-
if
|
|
275
|
+
if dataset is None:
|
|
269
276
|
raise HTTPException(
|
|
270
277
|
status_code=status.HTTP_404_NOT_FOUND, detail="Dataset not found"
|
|
271
278
|
)
|
|
272
|
-
if dataset.project_id != project_id:
|
|
273
|
-
raise HTTPException(
|
|
274
|
-
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
275
|
-
detail=f"Invalid {project_id=} for {dataset_id=}",
|
|
276
|
-
)
|
|
277
279
|
|
|
278
280
|
return dict(dataset=dataset, project=project)
|
|
279
281
|
|
|
@@ -299,8 +301,11 @@ async def _get_job_check_owner(
|
|
|
299
301
|
`project`).
|
|
300
302
|
|
|
301
303
|
Raises:
|
|
302
|
-
HTTPException(status_code=
|
|
303
|
-
If the job
|
|
304
|
+
HTTPException(status_code=404_UNPROCESSABLE_ENTITY):
|
|
305
|
+
If the project or the job do not exist or if they are not
|
|
306
|
+
associated
|
|
307
|
+
HTTPException(status_code=403_FORBIDDEN):
|
|
308
|
+
If the user is not a member of the project
|
|
304
309
|
"""
|
|
305
310
|
# Access control for project
|
|
306
311
|
project = await _get_project_check_owner(
|
|
@@ -308,17 +313,19 @@ async def _get_job_check_owner(
|
|
|
308
313
|
user_id=user_id,
|
|
309
314
|
db=db,
|
|
310
315
|
)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
316
|
+
|
|
317
|
+
res = await db.execute(
|
|
318
|
+
select(JobV2)
|
|
319
|
+
.where(JobV2.id == job_id)
|
|
320
|
+
.where(JobV2.project_id == project_id)
|
|
321
|
+
)
|
|
322
|
+
job = res.scalars().one_or_none()
|
|
323
|
+
|
|
324
|
+
if job is None:
|
|
314
325
|
raise HTTPException(
|
|
315
326
|
status_code=status.HTTP_404_NOT_FOUND, detail="Job not found"
|
|
316
327
|
)
|
|
317
|
-
|
|
318
|
-
raise HTTPException(
|
|
319
|
-
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
320
|
-
detail=f"Invalid {project_id=} for {job_id=}",
|
|
321
|
-
)
|
|
328
|
+
|
|
322
329
|
return dict(job=job, project=project)
|
|
323
330
|
|
|
324
331
|
|
|
@@ -411,8 +418,11 @@ async def _workflow_insert_task(
|
|
|
411
418
|
flag_modified(db_workflow, "task_list")
|
|
412
419
|
await db.commit()
|
|
413
420
|
|
|
414
|
-
|
|
415
|
-
|
|
421
|
+
wf_task = await db.get(
|
|
422
|
+
WorkflowTaskV2,
|
|
423
|
+
wf_task.id,
|
|
424
|
+
populate_existing=True, # See issue 1087
|
|
425
|
+
)
|
|
416
426
|
|
|
417
427
|
return wf_task
|
|
418
428
|
|
|
@@ -533,8 +543,7 @@ async def _get_submitted_job_or_none(
|
|
|
533
543
|
return res.scalars().one_or_none()
|
|
534
544
|
except MultipleResultsFound as e:
|
|
535
545
|
error_msg = (
|
|
536
|
-
"Multiple running jobs found for "
|
|
537
|
-
f"{dataset_id=} and {workflow_id=}."
|
|
546
|
+
f"Multiple running jobs found for {dataset_id=} and {workflow_id=}."
|
|
538
547
|
)
|
|
539
548
|
logger.error(f"{error_msg} Original error: {str(e)}.")
|
|
540
549
|
raise HTTPException(
|
|
@@ -546,10 +555,8 @@ async def _get_submitted_job_or_none(
|
|
|
546
555
|
async def _get_user_resource_id(user_id: int, db: AsyncSession) -> int | None:
|
|
547
556
|
res = await db.execute(
|
|
548
557
|
select(Resource.id)
|
|
549
|
-
.join(Profile)
|
|
550
|
-
.join(UserOAuth)
|
|
551
|
-
.where(Resource.id == Profile.resource_id)
|
|
552
|
-
.where(Profile.id == UserOAuth.profile_id)
|
|
558
|
+
.join(Profile, Resource.id == Profile.resource_id)
|
|
559
|
+
.join(UserOAuth, Profile.id == UserOAuth.profile_id)
|
|
553
560
|
.where(UserOAuth.id == user_id)
|
|
554
561
|
)
|
|
555
562
|
resource_id = res.scalar_one_or_none()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from typing import Literal
|
|
3
4
|
|
|
@@ -14,14 +15,12 @@ from fractal_server.app.routes.api.v2._aux_functions import _get_dataset_or_404
|
|
|
14
15
|
from fractal_server.app.routes.api.v2._aux_functions import (
|
|
15
16
|
_get_project_check_owner,
|
|
16
17
|
)
|
|
17
|
-
from fractal_server.app.routes.api.v2._aux_functions import
|
|
18
|
-
_get_workflow_or_404,
|
|
19
|
-
)
|
|
18
|
+
from fractal_server.app.routes.api.v2._aux_functions import _get_workflow_or_404
|
|
20
19
|
from fractal_server.app.routes.api.v2._aux_functions import (
|
|
21
20
|
_get_workflowtask_or_404,
|
|
22
21
|
)
|
|
23
22
|
from fractal_server.logger import set_logger
|
|
24
|
-
|
|
23
|
+
from fractal_server.zip_tools import _read_single_file_from_zip
|
|
25
24
|
|
|
26
25
|
logger = set_logger(__name__)
|
|
27
26
|
|
|
@@ -66,27 +65,51 @@ async def get_history_run_or_404(
|
|
|
66
65
|
|
|
67
66
|
def read_log_file(
|
|
68
67
|
*,
|
|
69
|
-
|
|
70
|
-
wftask: WorkflowTaskV2,
|
|
68
|
+
task_name: str,
|
|
71
69
|
dataset_id: int,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
f"{dataset_id} are not available."
|
|
81
|
-
)
|
|
70
|
+
logfile: str,
|
|
71
|
+
job_working_dir: str,
|
|
72
|
+
) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Returns the contents of a Job's log file, either directly from the working
|
|
75
|
+
directory or from the corresponding ZIP archive.
|
|
76
|
+
|
|
77
|
+
The function first checks if `logfile` exists on disk.
|
|
82
78
|
|
|
79
|
+
If not, it checks if the Job working directory has been zipped and tries to
|
|
80
|
+
read `logfile` from within the archive.
|
|
81
|
+
(Note: it is assumed that `logfile` is relative to `job_working_dir`)
|
|
82
|
+
"""
|
|
83
|
+
archive_path = os.path.normpath(job_working_dir) + ".zip"
|
|
83
84
|
try:
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
if Path(logfile).exists():
|
|
86
|
+
with open(logfile) as f:
|
|
87
|
+
return f.read()
|
|
88
|
+
elif Path(archive_path).exists():
|
|
89
|
+
relative_logfile = (
|
|
90
|
+
Path(logfile).relative_to(job_working_dir).as_posix()
|
|
91
|
+
)
|
|
92
|
+
return _read_single_file_from_zip(
|
|
93
|
+
file_path=relative_logfile, archive_path=archive_path
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
else:
|
|
97
|
+
logger.error(
|
|
98
|
+
f"Error while retrieving logs for {logfile=} and "
|
|
99
|
+
f"{archive_path=}: both files do not exist."
|
|
100
|
+
)
|
|
101
|
+
return (
|
|
102
|
+
f"Logs for task '{task_name}' in dataset "
|
|
103
|
+
f"{dataset_id} are not available."
|
|
104
|
+
)
|
|
86
105
|
except Exception as e:
|
|
106
|
+
logger.error(
|
|
107
|
+
f"Error while retrieving logs for {logfile=} and {archive_path=}. "
|
|
108
|
+
f"Original error: {str(e)}"
|
|
109
|
+
)
|
|
87
110
|
return (
|
|
88
|
-
f"Error while retrieving logs for task '{
|
|
89
|
-
f"in dataset {dataset_id}.
|
|
111
|
+
f"Error while retrieving logs for task '{task_name}' "
|
|
112
|
+
f"in dataset {dataset_id}."
|
|
90
113
|
)
|
|
91
114
|
|
|
92
115
|
|
|
@@ -99,8 +99,7 @@ async def get_package_version_from_pypi(
|
|
|
99
99
|
raise HTTPException(
|
|
100
100
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
101
101
|
detail=(
|
|
102
|
-
f"Could not get {url} (status_code {res.status_code})
|
|
103
|
-
f"\n{hint}"
|
|
102
|
+
f"Could not get {url} (status_code {res.status_code}).\n{hint}"
|
|
104
103
|
),
|
|
105
104
|
)
|
|
106
105
|
try:
|
|
@@ -144,8 +143,7 @@ async def get_package_version_from_pypi(
|
|
|
144
143
|
raise HTTPException(
|
|
145
144
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
146
145
|
detail=(
|
|
147
|
-
f"No version starting with {version} found.\n"
|
|
148
|
-
f"{hint}"
|
|
146
|
+
f"No version starting with {version} found.\n{hint}"
|
|
149
147
|
),
|
|
150
148
|
)
|
|
151
149
|
else:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Auxiliary functions to get task and task-group object from the database or
|
|
3
3
|
perform simple checks
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from fastapi import HTTPException
|
|
@@ -74,8 +75,7 @@ async def _get_task_group_read_access(
|
|
|
74
75
|
forbidden_exception = HTTPException(
|
|
75
76
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
76
77
|
detail=(
|
|
77
|
-
"Current user has no read access to TaskGroupV2 "
|
|
78
|
-
f"{task_group_id}."
|
|
78
|
+
f"Current user has no read access to TaskGroupV2 {task_group_id}."
|
|
79
79
|
),
|
|
80
80
|
)
|
|
81
81
|
|
|
@@ -86,13 +86,11 @@ async def _get_task_group_read_access(
|
|
|
86
86
|
else:
|
|
87
87
|
stm = (
|
|
88
88
|
select(LinkUserGroup)
|
|
89
|
-
.join(UserOAuth)
|
|
90
|
-
.join(Profile)
|
|
89
|
+
.join(UserOAuth, UserOAuth.id == LinkUserGroup.user_id)
|
|
90
|
+
.join(Profile, Profile.id == UserOAuth.profile_id)
|
|
91
91
|
.where(LinkUserGroup.group_id == task_group.user_group_id)
|
|
92
|
-
.where(LinkUserGroup.user_id == user_id)
|
|
93
92
|
.where(UserOAuth.id == user_id)
|
|
94
|
-
.where(Profile.
|
|
95
|
-
.where(task_group.resource_id == Profile.resource_id)
|
|
93
|
+
.where(Profile.resource_id == task_group.resource_id)
|
|
96
94
|
)
|
|
97
95
|
res = await db.execute(stm)
|
|
98
96
|
link = res.unique().scalars().one_or_none()
|
|
@@ -10,7 +10,6 @@ from fractal_server.exceptions import UnreachableBranchError
|
|
|
10
10
|
from fractal_server.logger import set_logger
|
|
11
11
|
from fractal_server.syringe import Inject
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
logger = set_logger(__name__)
|
|
15
14
|
|
|
16
15
|
|
|
@@ -84,7 +83,7 @@ async def _disambiguate_task_groups(
|
|
|
84
83
|
res = await db.execute(stm)
|
|
85
84
|
oldest_user_group_id = res.scalars().first()
|
|
86
85
|
logger.debug(
|
|
87
|
-
"[_disambiguate_task_groups]
|
|
86
|
+
f"[_disambiguate_task_groups] Result: {oldest_user_group_id=}."
|
|
88
87
|
)
|
|
89
88
|
task_group = next(
|
|
90
89
|
iter(
|
|
@@ -5,24 +5,24 @@ from fastapi import Response
|
|
|
5
5
|
from fastapi import status
|
|
6
6
|
from sqlmodel import select
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from ....models.v2 import DatasetV2
|
|
11
|
-
from ....models.v2 import JobV2
|
|
12
|
-
from ....models.v2 import ProjectV2
|
|
13
|
-
from ....schemas.v2 import DatasetCreateV2
|
|
14
|
-
from ....schemas.v2 import DatasetReadV2
|
|
15
|
-
from ....schemas.v2 import DatasetUpdateV2
|
|
16
|
-
from ....schemas.v2.dataset import DatasetExportV2
|
|
17
|
-
from ....schemas.v2.dataset import DatasetImportV2
|
|
18
|
-
from ._aux_functions import _get_dataset_check_owner
|
|
19
|
-
from ._aux_functions import _get_project_check_owner
|
|
20
|
-
from ._aux_functions import _get_submitted_jobs_statement
|
|
8
|
+
from fractal_server.app.db import AsyncSession
|
|
9
|
+
from fractal_server.app.db import get_async_db
|
|
21
10
|
from fractal_server.app.models import UserOAuth
|
|
11
|
+
from fractal_server.app.models.v2 import DatasetV2
|
|
12
|
+
from fractal_server.app.models.v2 import JobV2
|
|
22
13
|
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
14
|
+
from fractal_server.app.schemas.v2 import DatasetCreateV2
|
|
15
|
+
from fractal_server.app.schemas.v2 import DatasetReadV2
|
|
16
|
+
from fractal_server.app.schemas.v2 import DatasetUpdateV2
|
|
17
|
+
from fractal_server.app.schemas.v2.dataset import DatasetExportV2
|
|
18
|
+
from fractal_server.app.schemas.v2.dataset import DatasetImportV2
|
|
23
19
|
from fractal_server.string_tools import sanitize_string
|
|
24
20
|
from fractal_server.urls import normalize_url
|
|
25
21
|
|
|
22
|
+
from ._aux_functions import _get_dataset_check_owner
|
|
23
|
+
from ._aux_functions import _get_project_check_owner
|
|
24
|
+
from ._aux_functions import _get_submitted_jobs_statement
|
|
25
|
+
|
|
26
26
|
router = APIRouter()
|
|
27
27
|
|
|
28
28
|
|
|
@@ -208,25 +208,6 @@ async def delete_dataset(
|
|
|
208
208
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
209
209
|
|
|
210
210
|
|
|
211
|
-
@router.get("/dataset/", response_model=list[DatasetReadV2])
|
|
212
|
-
async def get_user_datasets(
|
|
213
|
-
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
214
|
-
db: AsyncSession = Depends(get_async_db),
|
|
215
|
-
) -> list[DatasetReadV2]:
|
|
216
|
-
"""
|
|
217
|
-
Returns all the datasets of the current user
|
|
218
|
-
"""
|
|
219
|
-
stm = select(DatasetV2)
|
|
220
|
-
stm = stm.join(ProjectV2).where(
|
|
221
|
-
ProjectV2.user_list.any(UserOAuth.id == user.id)
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
res = await db.execute(stm)
|
|
225
|
-
dataset_list = res.scalars().all()
|
|
226
|
-
await db.close()
|
|
227
|
-
return dataset_list
|
|
228
|
-
|
|
229
|
-
|
|
230
211
|
@router.get(
|
|
231
212
|
"/project/{project_id}/dataset/{dataset_id}/export/",
|
|
232
213
|
response_model=DatasetExportV2,
|