fractal-server 2.17.1a1__py3-none-any.whl → 2.18.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 +21 -19
- fractal_server/app/db/__init__.py +3 -3
- fractal_server/app/models/__init__.py +1 -0
- fractal_server/app/models/linkuserproject.py +43 -1
- fractal_server/app/models/security.py +28 -8
- 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 +17 -2
- fractal_server/app/models/v2/profile.py +29 -0
- fractal_server/app/models/v2/project.py +4 -10
- fractal_server/app/models/v2/resource.py +17 -0
- fractal_server/app/models/v2/task_group.py +4 -3
- fractal_server/app/models/v2/workflow.py +2 -1
- fractal_server/app/routes/admin/v2/__init__.py +12 -13
- fractal_server/app/routes/admin/v2/accounting.py +3 -3
- fractal_server/app/routes/admin/v2/job.py +35 -24
- 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/sharing.py +103 -0
- fractal_server/app/routes/admin/v2/task.py +37 -26
- fractal_server/app/routes/admin/v2/task_group.py +94 -17
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +21 -22
- fractal_server/app/routes/api/__init__.py +1 -9
- fractal_server/app/routes/api/v2/__init__.py +49 -50
- fractal_server/app/routes/api/v2/_aux_functions.py +132 -124
- fractal_server/app/routes/api/v2/_aux_functions_history.py +51 -23
- fractal_server/app/routes/api/v2/_aux_functions_sharing.py +97 -0
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +6 -8
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +7 -9
- fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +1 -2
- fractal_server/app/routes/api/v2/dataset.py +95 -102
- fractal_server/app/routes/api/v2/history.py +59 -33
- fractal_server/app/routes/api/v2/images.py +24 -9
- fractal_server/app/routes/api/v2/job.py +52 -33
- fractal_server/app/routes/api/v2/pre_submission_checks.py +16 -8
- fractal_server/app/routes/api/v2/project.py +65 -37
- fractal_server/app/routes/api/v2/sharing.py +311 -0
- fractal_server/app/routes/api/v2/status_legacy.py +31 -41
- fractal_server/app/routes/api/v2/submit.py +82 -78
- fractal_server/app/routes/api/v2/task.py +19 -20
- fractal_server/app/routes/api/v2/task_collection.py +41 -43
- fractal_server/app/routes/api/v2/task_collection_custom.py +19 -20
- fractal_server/app/routes/api/v2/task_collection_pixi.py +10 -11
- fractal_server/app/routes/api/v2/task_group.py +25 -24
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +32 -32
- fractal_server/app/routes/api/v2/task_version_update.py +23 -19
- fractal_server/app/routes/api/v2/workflow.py +50 -55
- fractal_server/app/routes/api/v2/workflow_import.py +37 -37
- fractal_server/app/routes/api/v2/workflowtask.py +32 -26
- fractal_server/app/routes/auth/__init__.py +1 -3
- fractal_server/app/routes/auth/_aux_auth.py +101 -2
- fractal_server/app/routes/auth/current_user.py +2 -66
- fractal_server/app/routes/auth/group.py +8 -35
- 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/router.py +2 -0
- fractal_server/app/routes/auth/users.py +19 -10
- fractal_server/app/routes/auth/viewer_paths.py +43 -0
- 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 +29 -12
- fractal_server/app/schemas/user_group.py +0 -15
- fractal_server/app/schemas/v2/__init__.py +55 -48
- fractal_server/app/schemas/v2/accounting.py +11 -0
- fractal_server/app/schemas/v2/dataset.py +57 -11
- fractal_server/app/schemas/v2/dumps.py +10 -9
- fractal_server/app/schemas/v2/job.py +11 -11
- fractal_server/app/schemas/v2/manifest.py +4 -3
- fractal_server/app/schemas/v2/profile.py +53 -2
- fractal_server/app/schemas/v2/project.py +3 -3
- fractal_server/app/schemas/v2/resource.py +121 -16
- fractal_server/app/schemas/v2/sharing.py +99 -0
- fractal_server/app/schemas/v2/status_legacy.py +3 -3
- fractal_server/app/schemas/v2/task.py +6 -7
- fractal_server/app/schemas/v2/task_collection.py +5 -5
- fractal_server/app/schemas/v2/task_group.py +16 -16
- fractal_server/app/schemas/v2/workflow.py +16 -16
- fractal_server/app/schemas/v2/workflowtask.py +16 -15
- fractal_server/app/security/__init__.py +5 -8
- fractal_server/app/security/signup_email.py +4 -5
- fractal_server/app/shutdown.py +6 -6
- fractal_server/config/__init__.py +0 -6
- fractal_server/config/_data.py +0 -68
- fractal_server/config/_database.py +19 -20
- fractal_server/config/_email.py +30 -38
- fractal_server/config/_main.py +38 -52
- fractal_server/config/_oauth.py +17 -21
- fractal_server/data_migrations/2_18_0.py +30 -0
- fractal_server/exceptions.py +4 -0
- fractal_server/images/models.py +4 -5
- fractal_server/images/status_tools.py +4 -2
- fractal_server/logger.py +1 -1
- fractal_server/main.py +75 -13
- 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 +1 -1
- fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +1 -0
- fractal_server/migrations/versions/49d0856e9569_drop_table.py +2 -3
- 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 +1 -1
- fractal_server/migrations/versions/7910eed4cf97_user_project_dirs_and_usergroup_viewer_.py +60 -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/88270f589c9b_add_prevent_new_submissions.py +39 -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/bc0e8b3327a7_project_sharing.py +72 -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 +1 -1
- 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/f0702066b007_one_submitted_job_per_dataset.py +40 -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 +39 -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 +9 -20
- 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 +2 -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 +12 -6
- fractal_server/runner/v2/_slurm_ssh.py +14 -7
- fractal_server/runner/v2/_slurm_sudo.py +14 -7
- fractal_server/runner/v2/db_tools.py +0 -1
- fractal_server/runner/v2/deduplicate_list.py +2 -1
- fractal_server/runner/v2/runner.py +44 -28
- fractal_server/runner/v2/runner_functions.py +22 -28
- fractal_server/runner/v2/submit_workflow.py +29 -15
- 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 +3 -3
- fractal_server/tasks/v2/local/collect.py +15 -18
- fractal_server/tasks/v2/local/collect_pixi.py +14 -16
- fractal_server/tasks/v2/local/deactivate.py +14 -15
- fractal_server/tasks/v2/local/deactivate_pixi.py +7 -7
- fractal_server/tasks/v2/local/delete.py +6 -8
- fractal_server/tasks/v2/local/reactivate.py +12 -12
- fractal_server/tasks/v2/local/reactivate_pixi.py +12 -12
- fractal_server/tasks/v2/ssh/_utils.py +3 -3
- fractal_server/tasks/v2/ssh/collect.py +19 -24
- fractal_server/tasks/v2/ssh/collect_pixi.py +22 -24
- fractal_server/tasks/v2/ssh/deactivate.py +17 -15
- fractal_server/tasks/v2/ssh/deactivate_pixi.py +8 -7
- fractal_server/tasks/v2/ssh/delete.py +12 -10
- fractal_server/tasks/v2/ssh/reactivate.py +16 -16
- fractal_server/tasks/v2/ssh/reactivate_pixi.py +13 -14
- 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 +10 -10
- fractal_server/tasks/v2/utils_database.py +5 -5
- fractal_server/tasks/v2/utils_package_names.py +1 -2
- fractal_server/tasks/v2/utils_pixi.py +1 -3
- fractal_server/types/__init__.py +98 -1
- fractal_server/types/validators/__init__.py +3 -0
- fractal_server/types/validators/_common_validators.py +33 -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.1a1.dist-info → fractal_server-2.18.0.dist-info}/METADATA +3 -2
- fractal_server-2.18.0.dist-info/RECORD +275 -0
- fractal_server/app/routes/admin/v2/project.py +0 -41
- fractal_server-2.17.1a1.dist-info/RECORD +0 -264
- {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.17.1a1.dist-info → fractal_server-2.18.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,8 +14,8 @@ from fractal_server.app.models.v2 import TaskGroupV2
|
|
|
14
14
|
from fractal_server.app.models.v2 import TaskV2
|
|
15
15
|
from fractal_server.app.models.v2 import WorkflowTaskV2
|
|
16
16
|
from fractal_server.app.models.v2 import WorkflowV2
|
|
17
|
-
from fractal_server.app.schemas.v2 import
|
|
18
|
-
from fractal_server.app.schemas.v2 import
|
|
17
|
+
from fractal_server.app.schemas.v2 import JobStatusType
|
|
18
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityStatus
|
|
19
19
|
from fractal_server.logger import set_logger
|
|
20
20
|
from fractal_server.tasks.v2.utils_package_names import normalize_package_name
|
|
21
21
|
|
|
@@ -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:
|
|
@@ -173,7 +171,7 @@ async def check_no_ongoing_activity(
|
|
|
173
171
|
stm = (
|
|
174
172
|
select(TaskGroupActivityV2)
|
|
175
173
|
.where(TaskGroupActivityV2.taskgroupv2_id == task_group_id)
|
|
176
|
-
.where(TaskGroupActivityV2.status ==
|
|
174
|
+
.where(TaskGroupActivityV2.status == TaskGroupActivityStatus.ONGOING)
|
|
177
175
|
)
|
|
178
176
|
res = await db.execute(stm)
|
|
179
177
|
ongoing_activities = res.scalars().all()
|
|
@@ -215,7 +213,7 @@ async def check_no_submitted_job(
|
|
|
215
213
|
.join(TaskV2, WorkflowTaskV2.task_id == TaskV2.id)
|
|
216
214
|
.where(WorkflowTaskV2.order >= JobV2.first_task_index)
|
|
217
215
|
.where(WorkflowTaskV2.order <= JobV2.last_task_index)
|
|
218
|
-
.where(JobV2.status ==
|
|
216
|
+
.where(JobV2.status == JobStatusType.SUBMITTED)
|
|
219
217
|
.where(TaskV2.taskgroupv2_id == task_group_id)
|
|
220
218
|
)
|
|
221
219
|
res = await db.execute(stm)
|
|
@@ -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
|
|
@@ -26,7 +27,7 @@ from fractal_server.app.routes.auth._aux_auth import (
|
|
|
26
27
|
from fractal_server.app.routes.auth._aux_auth import (
|
|
27
28
|
_verify_user_belongs_to_group,
|
|
28
29
|
)
|
|
29
|
-
from fractal_server.app.schemas.v2 import
|
|
30
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityAction
|
|
30
31
|
from fractal_server.images.tools import merge_type_filters
|
|
31
32
|
from fractal_server.logger import set_logger
|
|
32
33
|
|
|
@@ -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()
|
|
@@ -254,7 +252,7 @@ async def _get_collection_task_group_activity_status_message(
|
|
|
254
252
|
res = await db.execute(
|
|
255
253
|
select(TaskGroupActivityV2)
|
|
256
254
|
.where(TaskGroupActivityV2.taskgroupv2_id == task_group_id)
|
|
257
|
-
.where(TaskGroupActivityV2.action ==
|
|
255
|
+
.where(TaskGroupActivityV2.action == TaskGroupActivityAction.COLLECT)
|
|
258
256
|
)
|
|
259
257
|
task_group_activity_list = res.scalars().all()
|
|
260
258
|
if len(task_group_activity_list) > 1:
|
|
@@ -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(
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
1
4
|
from fastapi import APIRouter
|
|
2
5
|
from fastapi import Depends
|
|
3
6
|
from fastapi import HTTPException
|
|
@@ -5,89 +8,106 @@ from fastapi import Response
|
|
|
5
8
|
from fastapi import status
|
|
6
9
|
from sqlmodel import select
|
|
7
10
|
|
|
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
|
|
11
|
+
from fractal_server.app.db import AsyncSession
|
|
12
|
+
from fractal_server.app.db import get_async_db
|
|
21
13
|
from fractal_server.app.models import UserOAuth
|
|
14
|
+
from fractal_server.app.models.v2 import DatasetV2
|
|
15
|
+
from fractal_server.app.models.v2 import JobV2
|
|
22
16
|
from fractal_server.app.routes.auth import current_user_act_ver_prof
|
|
17
|
+
from fractal_server.app.schemas.v2 import DatasetCreate
|
|
18
|
+
from fractal_server.app.schemas.v2 import DatasetRead
|
|
19
|
+
from fractal_server.app.schemas.v2 import DatasetUpdate
|
|
20
|
+
from fractal_server.app.schemas.v2.dataset import DatasetExport
|
|
21
|
+
from fractal_server.app.schemas.v2.dataset import DatasetImport
|
|
22
|
+
from fractal_server.app.schemas.v2.sharing import ProjectPermissions
|
|
23
23
|
from fractal_server.string_tools import sanitize_string
|
|
24
24
|
from fractal_server.urls import normalize_url
|
|
25
25
|
|
|
26
|
+
from ._aux_functions import _get_dataset_check_access
|
|
27
|
+
from ._aux_functions import _get_project_check_access
|
|
28
|
+
from ._aux_functions import _get_submitted_jobs_statement
|
|
29
|
+
|
|
26
30
|
router = APIRouter()
|
|
27
31
|
|
|
28
32
|
|
|
29
33
|
@router.post(
|
|
30
34
|
"/project/{project_id}/dataset/",
|
|
31
|
-
response_model=
|
|
35
|
+
response_model=DatasetRead,
|
|
32
36
|
status_code=status.HTTP_201_CREATED,
|
|
33
37
|
)
|
|
34
38
|
async def create_dataset(
|
|
35
39
|
project_id: int,
|
|
36
|
-
dataset:
|
|
40
|
+
dataset: DatasetCreate,
|
|
37
41
|
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
38
42
|
db: AsyncSession = Depends(get_async_db),
|
|
39
|
-
) ->
|
|
43
|
+
) -> DatasetRead | None:
|
|
40
44
|
"""
|
|
41
45
|
Add new dataset to current project
|
|
42
46
|
"""
|
|
43
|
-
project = await
|
|
44
|
-
project_id=project_id,
|
|
47
|
+
project = await _get_project_check_access(
|
|
48
|
+
project_id=project_id,
|
|
49
|
+
user_id=user.id,
|
|
50
|
+
required_permissions=ProjectPermissions.WRITE,
|
|
51
|
+
db=db,
|
|
45
52
|
)
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
db_dataset = DatasetV2(
|
|
55
|
+
project_id=project_id,
|
|
56
|
+
zarr_dir="__PLACEHOLDER__",
|
|
57
|
+
**dataset.model_dump(exclude={"project_dir", "zarr_subfolder"}),
|
|
58
|
+
)
|
|
59
|
+
db.add(db_dataset)
|
|
60
|
+
await db.commit()
|
|
61
|
+
await db.refresh(db_dataset)
|
|
62
|
+
|
|
63
|
+
if dataset.project_dir is None:
|
|
64
|
+
project_dir = user.project_dirs[0]
|
|
65
|
+
else:
|
|
66
|
+
if dataset.project_dir not in user.project_dirs:
|
|
67
|
+
await db.delete(db_dataset)
|
|
68
|
+
await db.commit()
|
|
69
|
+
raise HTTPException(
|
|
70
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
71
|
+
detail=f"You are not allowed to use {dataset.project_dir=}.",
|
|
72
|
+
)
|
|
73
|
+
project_dir = dataset.project_dir
|
|
74
|
+
|
|
75
|
+
if dataset.zarr_subfolder is None:
|
|
76
|
+
zarr_subfolder = (
|
|
77
|
+
f"fractal/{project_id}_{sanitize_string(project.name)}/"
|
|
59
78
|
f"{db_dataset.id}_{sanitize_string(db_dataset.name)}"
|
|
60
79
|
)
|
|
61
|
-
normalized_path = normalize_url(path)
|
|
62
|
-
db_dataset.zarr_dir = normalized_path
|
|
63
|
-
|
|
64
|
-
db.add(db_dataset)
|
|
65
|
-
await db.commit()
|
|
66
|
-
await db.refresh(db_dataset)
|
|
67
80
|
else:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
zarr_subfolder = dataset.zarr_subfolder
|
|
82
|
+
|
|
83
|
+
zarr_dir = os.path.join(project_dir, zarr_subfolder)
|
|
84
|
+
db_dataset.zarr_dir = normalize_url(zarr_dir)
|
|
85
|
+
|
|
86
|
+
db.add(db_dataset)
|
|
87
|
+
await db.commit()
|
|
88
|
+
await db.refresh(db_dataset)
|
|
72
89
|
|
|
73
90
|
return db_dataset
|
|
74
91
|
|
|
75
92
|
|
|
76
93
|
@router.get(
|
|
77
94
|
"/project/{project_id}/dataset/",
|
|
78
|
-
response_model=list[
|
|
95
|
+
response_model=list[DatasetRead],
|
|
79
96
|
)
|
|
80
97
|
async def read_dataset_list(
|
|
81
98
|
project_id: int,
|
|
82
99
|
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
83
100
|
db: AsyncSession = Depends(get_async_db),
|
|
84
|
-
) -> list[
|
|
101
|
+
) -> list[DatasetRead] | None:
|
|
85
102
|
"""
|
|
86
103
|
Get dataset list for given project
|
|
87
104
|
"""
|
|
88
105
|
# Access control
|
|
89
|
-
project = await
|
|
90
|
-
project_id=project_id,
|
|
106
|
+
project = await _get_project_check_access(
|
|
107
|
+
project_id=project_id,
|
|
108
|
+
user_id=user.id,
|
|
109
|
+
required_permissions=ProjectPermissions.READ,
|
|
110
|
+
db=db,
|
|
91
111
|
)
|
|
92
112
|
# Find datasets of the current project. Note: this select/where approach
|
|
93
113
|
# has much better scaling than refreshing all elements of
|
|
@@ -96,72 +116,62 @@ async def read_dataset_list(
|
|
|
96
116
|
stm = select(DatasetV2).where(DatasetV2.project_id == project.id)
|
|
97
117
|
res = await db.execute(stm)
|
|
98
118
|
dataset_list = res.scalars().all()
|
|
99
|
-
await db.close()
|
|
100
119
|
return dataset_list
|
|
101
120
|
|
|
102
121
|
|
|
103
122
|
@router.get(
|
|
104
123
|
"/project/{project_id}/dataset/{dataset_id}/",
|
|
105
|
-
response_model=
|
|
124
|
+
response_model=DatasetRead,
|
|
106
125
|
)
|
|
107
126
|
async def read_dataset(
|
|
108
127
|
project_id: int,
|
|
109
128
|
dataset_id: int,
|
|
110
129
|
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
111
130
|
db: AsyncSession = Depends(get_async_db),
|
|
112
|
-
) ->
|
|
131
|
+
) -> DatasetRead | None:
|
|
113
132
|
"""
|
|
114
133
|
Get info on a dataset associated to the current project
|
|
115
134
|
"""
|
|
116
|
-
output = await
|
|
135
|
+
output = await _get_dataset_check_access(
|
|
117
136
|
project_id=project_id,
|
|
118
137
|
dataset_id=dataset_id,
|
|
119
138
|
user_id=user.id,
|
|
139
|
+
required_permissions=ProjectPermissions.READ,
|
|
120
140
|
db=db,
|
|
121
141
|
)
|
|
122
142
|
dataset = output["dataset"]
|
|
123
|
-
await db.close()
|
|
124
143
|
return dataset
|
|
125
144
|
|
|
126
145
|
|
|
127
146
|
@router.patch(
|
|
128
147
|
"/project/{project_id}/dataset/{dataset_id}/",
|
|
129
|
-
response_model=
|
|
148
|
+
response_model=DatasetRead,
|
|
130
149
|
)
|
|
131
150
|
async def update_dataset(
|
|
132
151
|
project_id: int,
|
|
133
152
|
dataset_id: int,
|
|
134
|
-
dataset_update:
|
|
153
|
+
dataset_update: DatasetUpdate,
|
|
135
154
|
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
136
155
|
db: AsyncSession = Depends(get_async_db),
|
|
137
|
-
) ->
|
|
156
|
+
) -> DatasetRead | None:
|
|
138
157
|
"""
|
|
139
158
|
Edit a dataset associated to the current project
|
|
140
159
|
"""
|
|
141
160
|
|
|
142
|
-
output = await
|
|
161
|
+
output = await _get_dataset_check_access(
|
|
143
162
|
project_id=project_id,
|
|
144
163
|
dataset_id=dataset_id,
|
|
145
164
|
user_id=user.id,
|
|
165
|
+
required_permissions=ProjectPermissions.WRITE,
|
|
146
166
|
db=db,
|
|
147
167
|
)
|
|
148
168
|
db_dataset = output["dataset"]
|
|
149
169
|
|
|
150
|
-
if (dataset_update.zarr_dir is not None) and (len(db_dataset.images) != 0):
|
|
151
|
-
raise HTTPException(
|
|
152
|
-
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
153
|
-
detail=(
|
|
154
|
-
"Cannot modify `zarr_dir` because the dataset has a non-empty "
|
|
155
|
-
"image list."
|
|
156
|
-
),
|
|
157
|
-
)
|
|
158
|
-
|
|
159
170
|
for key, value in dataset_update.model_dump(exclude_unset=True).items():
|
|
160
171
|
setattr(db_dataset, key, value)
|
|
161
172
|
|
|
162
173
|
await db.commit()
|
|
163
174
|
await db.refresh(db_dataset)
|
|
164
|
-
await db.close()
|
|
165
175
|
return db_dataset
|
|
166
176
|
|
|
167
177
|
|
|
@@ -178,10 +188,11 @@ async def delete_dataset(
|
|
|
178
188
|
"""
|
|
179
189
|
Delete a dataset associated to the current project
|
|
180
190
|
"""
|
|
181
|
-
output = await
|
|
191
|
+
output = await _get_dataset_check_access(
|
|
182
192
|
project_id=project_id,
|
|
183
193
|
dataset_id=dataset_id,
|
|
184
194
|
user_id=user.id,
|
|
195
|
+
required_permissions=ProjectPermissions.WRITE,
|
|
185
196
|
db=db,
|
|
186
197
|
)
|
|
187
198
|
dataset = output["dataset"]
|
|
@@ -208,46 +219,26 @@ async def delete_dataset(
|
|
|
208
219
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
209
220
|
|
|
210
221
|
|
|
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
222
|
@router.get(
|
|
231
223
|
"/project/{project_id}/dataset/{dataset_id}/export/",
|
|
232
|
-
response_model=
|
|
224
|
+
response_model=DatasetExport,
|
|
233
225
|
)
|
|
234
226
|
async def export_dataset(
|
|
235
227
|
project_id: int,
|
|
236
228
|
dataset_id: int,
|
|
237
229
|
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
238
230
|
db: AsyncSession = Depends(get_async_db),
|
|
239
|
-
) ->
|
|
231
|
+
) -> DatasetExport | None:
|
|
240
232
|
"""
|
|
241
233
|
Export an existing dataset
|
|
242
234
|
"""
|
|
243
|
-
dict_dataset_project = await
|
|
235
|
+
dict_dataset_project = await _get_dataset_check_access(
|
|
244
236
|
project_id=project_id,
|
|
245
237
|
dataset_id=dataset_id,
|
|
246
238
|
user_id=user.id,
|
|
239
|
+
required_permissions=ProjectPermissions.READ,
|
|
247
240
|
db=db,
|
|
248
241
|
)
|
|
249
|
-
await db.close()
|
|
250
|
-
|
|
251
242
|
dataset = dict_dataset_project["dataset"]
|
|
252
243
|
|
|
253
244
|
return dataset
|
|
@@ -255,35 +246,38 @@ async def export_dataset(
|
|
|
255
246
|
|
|
256
247
|
@router.post(
|
|
257
248
|
"/project/{project_id}/dataset/import/",
|
|
258
|
-
response_model=
|
|
249
|
+
response_model=DatasetRead,
|
|
259
250
|
status_code=status.HTTP_201_CREATED,
|
|
260
251
|
)
|
|
261
252
|
async def import_dataset(
|
|
262
253
|
project_id: int,
|
|
263
|
-
dataset:
|
|
254
|
+
dataset: DatasetImport,
|
|
264
255
|
user: UserOAuth = Depends(current_user_act_ver_prof),
|
|
265
256
|
db: AsyncSession = Depends(get_async_db),
|
|
266
|
-
) ->
|
|
257
|
+
) -> DatasetRead | None:
|
|
267
258
|
"""
|
|
268
259
|
Import an existing dataset into a project
|
|
269
260
|
"""
|
|
270
261
|
|
|
271
262
|
# Preliminary checks
|
|
272
|
-
await
|
|
263
|
+
await _get_project_check_access(
|
|
273
264
|
project_id=project_id,
|
|
274
265
|
user_id=user.id,
|
|
266
|
+
required_permissions=ProjectPermissions.WRITE,
|
|
275
267
|
db=db,
|
|
276
268
|
)
|
|
277
269
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
270
|
+
if not any(
|
|
271
|
+
Path(dataset.zarr_dir).is_relative_to(project_dir)
|
|
272
|
+
for project_dir in user.project_dirs
|
|
273
|
+
):
|
|
274
|
+
raise HTTPException(
|
|
275
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
276
|
+
detail=(
|
|
277
|
+
f"{dataset.zarr_dir=} is not relative to any of user's project "
|
|
278
|
+
"dirs."
|
|
279
|
+
),
|
|
280
|
+
)
|
|
287
281
|
|
|
288
282
|
# Create new Dataset
|
|
289
283
|
db_dataset = DatasetV2(
|
|
@@ -293,6 +287,5 @@ async def import_dataset(
|
|
|
293
287
|
db.add(db_dataset)
|
|
294
288
|
await db.commit()
|
|
295
289
|
await db.refresh(db_dataset)
|
|
296
|
-
await db.close()
|
|
297
290
|
|
|
298
291
|
return db_dataset
|