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
|
@@ -1,19 +1,27 @@
|
|
|
1
|
+
from os.path import normpath
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
1
4
|
from fastapi import HTTPException
|
|
2
5
|
from fastapi import status
|
|
3
6
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
7
|
+
from sqlmodel import and_
|
|
4
8
|
from sqlmodel import asc
|
|
9
|
+
from sqlmodel import not_
|
|
10
|
+
from sqlmodel import or_
|
|
5
11
|
from sqlmodel import select
|
|
6
12
|
|
|
7
13
|
from fractal_server.app.models.linkusergroup import LinkUserGroup
|
|
14
|
+
from fractal_server.app.models.linkuserproject import LinkUserProjectV2
|
|
8
15
|
from fractal_server.app.models.security import UserGroup
|
|
9
16
|
from fractal_server.app.models.security import UserOAuth
|
|
17
|
+
from fractal_server.app.models.v2.dataset import DatasetV2
|
|
18
|
+
from fractal_server.app.models.v2.project import ProjectV2
|
|
10
19
|
from fractal_server.app.schemas.user import UserRead
|
|
11
20
|
from fractal_server.app.schemas.user_group import UserGroupRead
|
|
12
21
|
from fractal_server.config import get_settings
|
|
13
22
|
from fractal_server.logger import set_logger
|
|
14
23
|
from fractal_server.syringe import Inject
|
|
15
24
|
|
|
16
|
-
|
|
17
25
|
logger = set_logger(__name__)
|
|
18
26
|
|
|
19
27
|
|
|
@@ -36,7 +44,7 @@ async def _get_single_user_with_groups(
|
|
|
36
44
|
|
|
37
45
|
stm_groups = (
|
|
38
46
|
select(UserGroup)
|
|
39
|
-
.join(LinkUserGroup)
|
|
47
|
+
.join(LinkUserGroup, LinkUserGroup.group_id == UserGroup.id)
|
|
40
48
|
.where(LinkUserGroup.user_id == user.id)
|
|
41
49
|
.order_by(asc(LinkUserGroup.timestamp_created))
|
|
42
50
|
)
|
|
@@ -179,3 +187,94 @@ async def _verify_user_belongs_to_group(
|
|
|
179
187
|
f"to UserGroup {user_group_id}"
|
|
180
188
|
),
|
|
181
189
|
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
async def _check_project_dirs_update(
|
|
193
|
+
*,
|
|
194
|
+
old_project_dirs: list[str],
|
|
195
|
+
new_project_dirs: list[str],
|
|
196
|
+
user_id: int,
|
|
197
|
+
db: AsyncSession,
|
|
198
|
+
) -> None:
|
|
199
|
+
"""
|
|
200
|
+
Raises 422 if by replacing user's `project_dirs` with new ones we are
|
|
201
|
+
removing the access to a `zarr_dir` used by some dataset.
|
|
202
|
+
|
|
203
|
+
Note both `old_project_dirs` and `new_project_dirs` have been
|
|
204
|
+
normalized through `os.path.normpath`, which notably strips any trailing
|
|
205
|
+
`/` character. To be safe, we also re-normalize them within this function.
|
|
206
|
+
"""
|
|
207
|
+
# Create a list of all the old project dirs that will lose privileges.
|
|
208
|
+
# E.g.:
|
|
209
|
+
# old_project_dirs = ["/a", "/b", "/c/d", "/e/f"]
|
|
210
|
+
# new_project_dirs = ["/a", "/c", "/e/f/g1", "/e/f/g2"]
|
|
211
|
+
# removed_project_dirs == ["/b", "/e/f"]
|
|
212
|
+
removed_project_dirs = [
|
|
213
|
+
old_project_dir
|
|
214
|
+
for old_project_dir in old_project_dirs
|
|
215
|
+
if not any(
|
|
216
|
+
Path(old_project_dir).is_relative_to(new_project_dir)
|
|
217
|
+
for new_project_dir in new_project_dirs
|
|
218
|
+
)
|
|
219
|
+
]
|
|
220
|
+
if removed_project_dirs:
|
|
221
|
+
# Query all the `zarr_dir`s linked to the user such that `zarr_dir`
|
|
222
|
+
# starts with one of the project dirs in `removed_project_dirs`.
|
|
223
|
+
stmt = (
|
|
224
|
+
select(DatasetV2.zarr_dir)
|
|
225
|
+
.join(ProjectV2, ProjectV2.id == DatasetV2.project_id)
|
|
226
|
+
.join(
|
|
227
|
+
LinkUserProjectV2,
|
|
228
|
+
LinkUserProjectV2.project_id == ProjectV2.id,
|
|
229
|
+
)
|
|
230
|
+
.where(LinkUserProjectV2.user_id == user_id)
|
|
231
|
+
.where(LinkUserProjectV2.is_verified.is_(True))
|
|
232
|
+
.where(
|
|
233
|
+
or_(
|
|
234
|
+
*[
|
|
235
|
+
DatasetV2.zarr_dir.startswith(normpath(old_project_dir))
|
|
236
|
+
for old_project_dir in removed_project_dirs
|
|
237
|
+
]
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
if new_project_dirs:
|
|
242
|
+
stmt = stmt.where(
|
|
243
|
+
and_(
|
|
244
|
+
*[
|
|
245
|
+
not_(
|
|
246
|
+
DatasetV2.zarr_dir.startswith(
|
|
247
|
+
normpath(new_project_dir)
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
for new_project_dir in new_project_dirs
|
|
251
|
+
]
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
res = await db.execute(stmt)
|
|
255
|
+
|
|
256
|
+
# Raise 422 if one of the query results is relative to a path in
|
|
257
|
+
# `removed_project_dirs`, but its not relative to any path in
|
|
258
|
+
# `new_project_dirs`.
|
|
259
|
+
if any(
|
|
260
|
+
(
|
|
261
|
+
any(
|
|
262
|
+
Path(zarr_dir).is_relative_to(old_project_dir)
|
|
263
|
+
for old_project_dir in removed_project_dirs
|
|
264
|
+
)
|
|
265
|
+
and not any(
|
|
266
|
+
Path(zarr_dir).is_relative_to(new_project_dir)
|
|
267
|
+
for new_project_dir in new_project_dirs
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
for zarr_dir in res.scalars().all()
|
|
271
|
+
):
|
|
272
|
+
raise HTTPException(
|
|
273
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
274
|
+
detail=(
|
|
275
|
+
"You tried updating the user project_dirs, removing "
|
|
276
|
+
f"{removed_project_dirs}. This operation is not possible, "
|
|
277
|
+
"because it would make the user loose access to some of "
|
|
278
|
+
"their dataset zarr directories."
|
|
279
|
+
),
|
|
280
|
+
)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Definition of `/auth/current-user/` endpoints
|
|
3
3
|
"""
|
|
4
|
-
import os
|
|
5
4
|
|
|
6
5
|
from fastapi import APIRouter
|
|
7
6
|
from fastapi import Depends
|
|
@@ -9,13 +8,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
9
8
|
from sqlmodel import select
|
|
10
9
|
|
|
11
10
|
from fractal_server.app.db import get_async_db
|
|
12
|
-
from fractal_server.app.models import LinkUserGroup
|
|
13
11
|
from fractal_server.app.models import Profile
|
|
14
12
|
from fractal_server.app.models import Resource
|
|
15
|
-
from fractal_server.app.models import UserGroup
|
|
16
13
|
from fractal_server.app.models import UserOAuth
|
|
17
14
|
from fractal_server.app.routes.auth import current_user_act
|
|
18
|
-
from fractal_server.app.routes.auth import current_user_act_ver
|
|
19
15
|
from fractal_server.app.routes.auth._aux_auth import (
|
|
20
16
|
_get_single_user_with_groups,
|
|
21
17
|
)
|
|
@@ -23,11 +19,8 @@ from fractal_server.app.schemas import UserProfileInfo
|
|
|
23
19
|
from fractal_server.app.schemas.user import UserRead
|
|
24
20
|
from fractal_server.app.schemas.user import UserUpdate
|
|
25
21
|
from fractal_server.app.schemas.user import UserUpdateStrict
|
|
26
|
-
from fractal_server.app.security import get_user_manager
|
|
27
22
|
from fractal_server.app.security import UserManager
|
|
28
|
-
from fractal_server.
|
|
29
|
-
from fractal_server.config import get_data_settings
|
|
30
|
-
from fractal_server.syringe import Inject
|
|
23
|
+
from fractal_server.app.security import get_user_manager
|
|
31
24
|
|
|
32
25
|
router_current_user = APIRouter()
|
|
33
26
|
|
|
@@ -87,9 +80,8 @@ async def get_current_user_profile_info(
|
|
|
87
80
|
) -> UserProfileInfo:
|
|
88
81
|
stm = (
|
|
89
82
|
select(Resource, Profile)
|
|
90
|
-
.join(UserOAuth)
|
|
83
|
+
.join(UserOAuth, Profile.id == UserOAuth.profile_id)
|
|
91
84
|
.where(Resource.id == Profile.resource_id)
|
|
92
|
-
.where(Profile.id == UserOAuth.profile_id)
|
|
93
85
|
.where(UserOAuth.id == current_user.id)
|
|
94
86
|
)
|
|
95
87
|
res = await db.execute(stm)
|
|
@@ -106,59 +98,3 @@ async def get_current_user_profile_info(
|
|
|
106
98
|
)
|
|
107
99
|
|
|
108
100
|
return response_data
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
@router_current_user.get(
|
|
112
|
-
"/current-user/allowed-viewer-paths/", response_model=list[str]
|
|
113
|
-
)
|
|
114
|
-
async def get_current_user_allowed_viewer_paths(
|
|
115
|
-
current_user: UserOAuth = Depends(current_user_act_ver),
|
|
116
|
-
db: AsyncSession = Depends(get_async_db),
|
|
117
|
-
) -> list[str]:
|
|
118
|
-
"""
|
|
119
|
-
Returns the allowed viewer paths for current user, according to the
|
|
120
|
-
selected FRACTAL_DATA_AUTH_SCHEME
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
data_settings = Inject(get_data_settings)
|
|
124
|
-
|
|
125
|
-
authorized_paths = []
|
|
126
|
-
|
|
127
|
-
if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.NONE:
|
|
128
|
-
return authorized_paths
|
|
129
|
-
|
|
130
|
-
# Append `project_dir` to the list of authorized paths
|
|
131
|
-
authorized_paths.append(current_user.project_dir)
|
|
132
|
-
|
|
133
|
-
# If auth scheme is "users-folders" and `slurm_user` is set,
|
|
134
|
-
# build and append the user folder
|
|
135
|
-
if (
|
|
136
|
-
data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.USERS_FOLDERS
|
|
137
|
-
and current_user.profile_id is not None
|
|
138
|
-
):
|
|
139
|
-
profile = await db.get(Profile, current_user.profile_id)
|
|
140
|
-
if profile is not None and profile.username is not None:
|
|
141
|
-
base_folder = data_settings.FRACTAL_DATA_BASE_FOLDER
|
|
142
|
-
user_folder = os.path.join(base_folder, profile.username)
|
|
143
|
-
authorized_paths.append(user_folder)
|
|
144
|
-
|
|
145
|
-
if data_settings.FRACTAL_DATA_AUTH_SCHEME == DataAuthScheme.VIEWER_PATHS:
|
|
146
|
-
# Returns the union of `viewer_paths` for all user's groups
|
|
147
|
-
cmd = (
|
|
148
|
-
select(UserGroup.viewer_paths)
|
|
149
|
-
.join(LinkUserGroup)
|
|
150
|
-
.where(LinkUserGroup.group_id == UserGroup.id)
|
|
151
|
-
.where(LinkUserGroup.user_id == current_user.id)
|
|
152
|
-
)
|
|
153
|
-
res = await db.execute(cmd)
|
|
154
|
-
viewer_paths_nested = res.scalars().all()
|
|
155
|
-
|
|
156
|
-
# Flatten a nested object and make its elements unique
|
|
157
|
-
all_viewer_paths_set = {
|
|
158
|
-
path
|
|
159
|
-
for _viewer_paths in viewer_paths_nested
|
|
160
|
-
for path in _viewer_paths
|
|
161
|
-
}
|
|
162
|
-
authorized_paths.extend(all_viewer_paths_set)
|
|
163
|
-
|
|
164
|
-
return authorized_paths
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Definition of `/auth/group/` routes
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from fastapi import APIRouter
|
|
5
6
|
from fastapi import Depends
|
|
6
7
|
from fastapi import HTTPException
|
|
@@ -9,22 +10,22 @@ from fastapi import status
|
|
|
9
10
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
11
|
from sqlmodel import select
|
|
11
12
|
|
|
12
|
-
from . import current_superuser_act
|
|
13
|
-
from ._aux_auth import _get_default_usergroup_id_or_none
|
|
14
|
-
from ._aux_auth import _get_single_usergroup_with_user_ids
|
|
15
|
-
from ._aux_auth import _user_or_404
|
|
16
|
-
from ._aux_auth import _usergroup_or_404
|
|
17
13
|
from fractal_server.app.db import get_async_db
|
|
18
14
|
from fractal_server.app.models import LinkUserGroup
|
|
19
15
|
from fractal_server.app.models import UserGroup
|
|
20
16
|
from fractal_server.app.models import UserOAuth
|
|
21
17
|
from fractal_server.app.schemas.user_group import UserGroupCreate
|
|
22
18
|
from fractal_server.app.schemas.user_group import UserGroupRead
|
|
23
|
-
from fractal_server.app.schemas.user_group import UserGroupUpdate
|
|
24
19
|
from fractal_server.config import get_settings
|
|
25
20
|
from fractal_server.logger import set_logger
|
|
26
21
|
from fractal_server.syringe import Inject
|
|
27
22
|
|
|
23
|
+
from . import current_superuser_act
|
|
24
|
+
from ._aux_auth import _get_default_usergroup_id_or_none
|
|
25
|
+
from ._aux_auth import _get_single_usergroup_with_user_ids
|
|
26
|
+
from ._aux_auth import _user_or_404
|
|
27
|
+
from ._aux_auth import _usergroup_or_404
|
|
28
|
+
|
|
28
29
|
logger = set_logger(__name__)
|
|
29
30
|
|
|
30
31
|
|
|
@@ -99,41 +100,13 @@ async def create_single_group(
|
|
|
99
100
|
)
|
|
100
101
|
|
|
101
102
|
# Create and return new group
|
|
102
|
-
new_group = UserGroup(
|
|
103
|
-
name=group_create.name, viewer_paths=group_create.viewer_paths
|
|
104
|
-
)
|
|
103
|
+
new_group = UserGroup(name=group_create.name)
|
|
105
104
|
db.add(new_group)
|
|
106
105
|
await db.commit()
|
|
107
106
|
|
|
108
107
|
return dict(new_group.model_dump(), user_ids=[])
|
|
109
108
|
|
|
110
109
|
|
|
111
|
-
@router_group.patch(
|
|
112
|
-
"/group/{group_id}/",
|
|
113
|
-
response_model=UserGroupRead,
|
|
114
|
-
status_code=status.HTTP_200_OK,
|
|
115
|
-
)
|
|
116
|
-
async def update_single_group(
|
|
117
|
-
group_id: int,
|
|
118
|
-
group_update: UserGroupUpdate,
|
|
119
|
-
user: UserOAuth = Depends(current_superuser_act),
|
|
120
|
-
db: AsyncSession = Depends(get_async_db),
|
|
121
|
-
) -> UserGroupRead:
|
|
122
|
-
group = await _usergroup_or_404(group_id, db)
|
|
123
|
-
|
|
124
|
-
# Patch `viewer_paths`
|
|
125
|
-
if group_update.viewer_paths is not None:
|
|
126
|
-
group.viewer_paths = group_update.viewer_paths
|
|
127
|
-
db.add(group)
|
|
128
|
-
await db.commit()
|
|
129
|
-
|
|
130
|
-
updated_group = await _get_single_usergroup_with_user_ids(
|
|
131
|
-
group_id=group_id, db=db
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
return updated_group
|
|
135
|
-
|
|
136
|
-
|
|
137
110
|
@router_group.delete("/group/{group_id}/", status_code=204)
|
|
138
111
|
async def delete_single_group(
|
|
139
112
|
group_id: int,
|
|
@@ -4,13 +4,14 @@ from httpx_oauth.clients.google import GoogleOAuth2
|
|
|
4
4
|
from httpx_oauth.clients.openid import OpenID
|
|
5
5
|
from httpx_oauth.clients.openid import OpenIDConfigurationError
|
|
6
6
|
|
|
7
|
-
from . import
|
|
8
|
-
from . import fastapi_users
|
|
7
|
+
from fractal_server.config import OAuthSettings
|
|
9
8
|
from fractal_server.config import get_oauth_settings
|
|
10
9
|
from fractal_server.config import get_settings
|
|
11
|
-
from fractal_server.config import OAuthSettings
|
|
12
10
|
from fractal_server.syringe import Inject
|
|
13
11
|
|
|
12
|
+
from . import cookie_backend
|
|
13
|
+
from . import fastapi_users
|
|
14
|
+
|
|
14
15
|
|
|
15
16
|
def _create_client_github(cfg: OAuthSettings) -> GitHubOAuth2:
|
|
16
17
|
return GitHubOAuth2(
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Definition of `/auth/register/` routes.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from fastapi import APIRouter
|
|
5
6
|
from fastapi import Depends
|
|
6
7
|
|
|
8
|
+
from fractal_server.app.schemas.user import UserCreate
|
|
9
|
+
from fractal_server.app.schemas.user import UserRead
|
|
10
|
+
|
|
7
11
|
from . import current_superuser_act
|
|
8
12
|
from . import fastapi_users
|
|
9
|
-
from ...schemas.user import UserCreate
|
|
10
|
-
from ...schemas.user import UserRead
|
|
11
13
|
|
|
12
14
|
router_register = APIRouter()
|
|
13
15
|
|
|
@@ -6,6 +6,7 @@ from .login import router_login
|
|
|
6
6
|
from .oauth import get_oauth_router
|
|
7
7
|
from .register import router_register
|
|
8
8
|
from .users import router_users
|
|
9
|
+
from .viewer_paths import router_viewer_paths
|
|
9
10
|
|
|
10
11
|
router_auth = APIRouter()
|
|
11
12
|
|
|
@@ -14,6 +15,7 @@ router_auth.include_router(router_current_user)
|
|
|
14
15
|
router_auth.include_router(router_login)
|
|
15
16
|
router_auth.include_router(router_users)
|
|
16
17
|
router_auth.include_router(router_group)
|
|
18
|
+
router_auth.include_router(router_viewer_paths)
|
|
17
19
|
router_oauth = get_oauth_router()
|
|
18
20
|
if router_oauth is not None:
|
|
19
21
|
router_auth.include_router(router_oauth)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Definition of `/auth/users/` routes
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from fastapi import APIRouter
|
|
5
6
|
from fastapi import Depends
|
|
6
7
|
from fastapi import HTTPException
|
|
@@ -11,24 +12,25 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
11
12
|
from sqlmodel import func
|
|
12
13
|
from sqlmodel import select
|
|
13
14
|
|
|
14
|
-
from . import
|
|
15
|
-
from ...db import get_async_db
|
|
16
|
-
from ...schemas.user import UserRead
|
|
17
|
-
from ...schemas.user import UserUpdate
|
|
18
|
-
from ._aux_auth import _get_default_usergroup_id_or_none
|
|
19
|
-
from ._aux_auth import _get_single_user_with_groups
|
|
15
|
+
from fractal_server.app.db import get_async_db
|
|
20
16
|
from fractal_server.app.models import LinkUserGroup
|
|
21
17
|
from fractal_server.app.models import UserGroup
|
|
22
18
|
from fractal_server.app.models import UserOAuth
|
|
23
19
|
from fractal_server.app.models.v2 import Profile
|
|
24
20
|
from fractal_server.app.routes.auth._aux_auth import _user_or_404
|
|
21
|
+
from fractal_server.app.schemas.user import UserRead
|
|
22
|
+
from fractal_server.app.schemas.user import UserUpdate
|
|
25
23
|
from fractal_server.app.schemas.user import UserUpdateGroups
|
|
26
|
-
from fractal_server.app.security import get_user_manager
|
|
27
24
|
from fractal_server.app.security import UserManager
|
|
25
|
+
from fractal_server.app.security import get_user_manager
|
|
28
26
|
from fractal_server.config import get_settings
|
|
29
27
|
from fractal_server.logger import set_logger
|
|
30
28
|
from fractal_server.syringe import Inject
|
|
31
29
|
|
|
30
|
+
from . import current_superuser_act
|
|
31
|
+
from ._aux_auth import _check_project_dirs_update
|
|
32
|
+
from ._aux_auth import _get_default_usergroup_id_or_none
|
|
33
|
+
from ._aux_auth import _get_single_user_with_groups
|
|
32
34
|
|
|
33
35
|
router_users = APIRouter()
|
|
34
36
|
|
|
@@ -73,6 +75,14 @@ async def patch_user(
|
|
|
73
75
|
detail=f"Profile {user_update.profile_id} not found.",
|
|
74
76
|
)
|
|
75
77
|
|
|
78
|
+
if user_update.project_dirs is not None:
|
|
79
|
+
await _check_project_dirs_update(
|
|
80
|
+
old_project_dirs=user_to_patch.project_dirs,
|
|
81
|
+
new_project_dirs=user_update.project_dirs,
|
|
82
|
+
user_id=user_id,
|
|
83
|
+
db=db,
|
|
84
|
+
)
|
|
85
|
+
|
|
76
86
|
# Modify user attributes
|
|
77
87
|
try:
|
|
78
88
|
user = await user_manager.update(
|
|
@@ -197,13 +207,12 @@ async def set_user_groups(
|
|
|
197
207
|
# Remove/create links as needed
|
|
198
208
|
for link in links_to_remove:
|
|
199
209
|
logger.info(
|
|
200
|
-
f"Removing LinkUserGroup with {link.user_id=} "
|
|
201
|
-
f"and {link.group_id=}."
|
|
210
|
+
f"Removing LinkUserGroup with {link.user_id=} and {link.group_id=}."
|
|
202
211
|
)
|
|
203
212
|
await db.delete(link)
|
|
204
213
|
for group_id in ids_links_to_add:
|
|
205
214
|
logger.info(
|
|
206
|
-
f"Creating new LinkUserGroup with {user_id=}
|
|
215
|
+
f"Creating new LinkUserGroup with {user_id=} and {group_id=}."
|
|
207
216
|
)
|
|
208
217
|
db.add(LinkUserGroup(user_id=user_id, group_id=group_id))
|
|
209
218
|
await db.commit()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
from fastapi import Depends
|
|
3
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
|
+
from sqlmodel import select
|
|
5
|
+
|
|
6
|
+
from fractal_server.app.db import get_async_db
|
|
7
|
+
from fractal_server.app.models import UserOAuth
|
|
8
|
+
from fractal_server.app.models.linkuserproject import LinkUserProjectV2
|
|
9
|
+
from fractal_server.app.models.v2.dataset import DatasetV2
|
|
10
|
+
from fractal_server.app.models.v2.project import ProjectV2
|
|
11
|
+
from fractal_server.app.routes.auth import current_user_act_ver
|
|
12
|
+
|
|
13
|
+
router_viewer_paths = APIRouter()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@router_viewer_paths.get(
|
|
17
|
+
"/current-user/allowed-viewer-paths/", response_model=list[str]
|
|
18
|
+
)
|
|
19
|
+
async def get_current_user_allowed_viewer_paths(
|
|
20
|
+
include_shared_projects: bool = True,
|
|
21
|
+
current_user: UserOAuth = Depends(current_user_act_ver),
|
|
22
|
+
db: AsyncSession = Depends(get_async_db),
|
|
23
|
+
) -> list[str]:
|
|
24
|
+
"""
|
|
25
|
+
Returns the allowed viewer paths for current user.
|
|
26
|
+
"""
|
|
27
|
+
authorized_paths = current_user.project_dirs.copy()
|
|
28
|
+
|
|
29
|
+
if include_shared_projects:
|
|
30
|
+
res = await db.execute(
|
|
31
|
+
select(DatasetV2.zarr_dir)
|
|
32
|
+
.join(ProjectV2, ProjectV2.id == DatasetV2.project_id)
|
|
33
|
+
.join(
|
|
34
|
+
LinkUserProjectV2, LinkUserProjectV2.project_id == ProjectV2.id
|
|
35
|
+
)
|
|
36
|
+
.where(LinkUserProjectV2.user_id == current_user.id)
|
|
37
|
+
.where(LinkUserProjectV2.is_verified.is_(True))
|
|
38
|
+
)
|
|
39
|
+
authorized_paths.extend(res.unique().scalars().all())
|
|
40
|
+
# Note that `project_dirs` and the `db.execute` result may have some
|
|
41
|
+
# common elements, and then this list may have non-unique items.
|
|
42
|
+
|
|
43
|
+
return authorized_paths
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from fastapi import HTTPException
|
|
2
2
|
from fastapi import status
|
|
3
3
|
|
|
4
|
-
from ....config import get_settings
|
|
5
|
-
from ....syringe import Inject
|
|
6
4
|
from fractal_server.app.schemas.v2 import ResourceType
|
|
5
|
+
from fractal_server.config import get_settings
|
|
6
|
+
from fractal_server.syringe import Inject
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def _backend_supports_shutdown(backend: str) -> bool:
|
|
@@ -4,8 +4,8 @@ from typing import TypeVar
|
|
|
4
4
|
from fastapi import HTTPException
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
from pydantic import Field
|
|
7
|
-
from pydantic import model_validator
|
|
8
7
|
from pydantic import ValidationError
|
|
8
|
+
from pydantic import model_validator
|
|
9
9
|
|
|
10
10
|
T = TypeVar("T")
|
|
11
11
|
|
|
@@ -8,12 +8,18 @@ from pydantic import EmailStr
|
|
|
8
8
|
from pydantic import Field
|
|
9
9
|
|
|
10
10
|
from fractal_server.string_tools import validate_cmd
|
|
11
|
-
from fractal_server.types import
|
|
11
|
+
from fractal_server.types import ListUniqueAbsolutePathStr
|
|
12
12
|
from fractal_server.types import ListUniqueNonEmptyString
|
|
13
13
|
from fractal_server.types import ListUniqueNonNegativeInt
|
|
14
14
|
from fractal_server.types import NonEmptyStr
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def _validate_cmd_list(value: list[str]) -> list[str]:
|
|
18
|
+
for v in value:
|
|
19
|
+
validate_cmd(v)
|
|
20
|
+
return value
|
|
21
|
+
|
|
22
|
+
|
|
17
23
|
class OAuthAccountRead(BaseModel):
|
|
18
24
|
"""
|
|
19
25
|
Schema for storing essential `OAuthAccount` information within
|
|
@@ -38,20 +44,17 @@ class UserRead(schemas.BaseUser[int]):
|
|
|
38
44
|
group_ids_names:
|
|
39
45
|
oauth_accounts:
|
|
40
46
|
profile_id:
|
|
47
|
+
project_dirs:
|
|
48
|
+
slurm_accounts:
|
|
41
49
|
"""
|
|
42
50
|
|
|
43
51
|
group_ids_names: list[tuple[int, str]] | None = None
|
|
44
52
|
oauth_accounts: list[OAuthAccountRead]
|
|
45
53
|
profile_id: int | None = None
|
|
46
|
-
|
|
54
|
+
project_dirs: list[str]
|
|
47
55
|
slurm_accounts: list[str]
|
|
48
56
|
|
|
49
57
|
|
|
50
|
-
def _validate_cmd(value: str) -> str:
|
|
51
|
-
validate_cmd(value)
|
|
52
|
-
return value
|
|
53
|
-
|
|
54
|
-
|
|
55
58
|
class UserUpdate(schemas.BaseUserUpdate):
|
|
56
59
|
"""
|
|
57
60
|
Schema for `User` update.
|
|
@@ -63,7 +66,7 @@ class UserUpdate(schemas.BaseUserUpdate):
|
|
|
63
66
|
is_superuser:
|
|
64
67
|
is_verified:
|
|
65
68
|
profile_id:
|
|
66
|
-
|
|
69
|
+
project_dirs:
|
|
67
70
|
slurm_accounts:
|
|
68
71
|
"""
|
|
69
72
|
|
|
@@ -74,9 +77,9 @@ class UserUpdate(schemas.BaseUserUpdate):
|
|
|
74
77
|
is_superuser: bool = None
|
|
75
78
|
is_verified: bool = None
|
|
76
79
|
profile_id: int | None = None
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
] = None
|
|
80
|
+
project_dirs: Annotated[
|
|
81
|
+
ListUniqueAbsolutePathStr, AfterValidator(_validate_cmd_list)
|
|
82
|
+
] = Field(default=None, min_length=1)
|
|
80
83
|
slurm_accounts: ListUniqueNonEmptyString = None
|
|
81
84
|
|
|
82
85
|
|
|
@@ -98,10 +101,14 @@ class UserCreate(schemas.BaseUserCreate):
|
|
|
98
101
|
|
|
99
102
|
Attributes:
|
|
100
103
|
profile_id:
|
|
104
|
+
project_dirs:
|
|
105
|
+
slurm_accounts:
|
|
101
106
|
"""
|
|
102
107
|
|
|
103
108
|
profile_id: int | None = None
|
|
104
|
-
|
|
109
|
+
project_dirs: Annotated[
|
|
110
|
+
ListUniqueAbsolutePathStr, AfterValidator(_validate_cmd_list)
|
|
111
|
+
] = Field(min_length=1)
|
|
105
112
|
slurm_accounts: list[str] = Field(default_factory=list)
|
|
106
113
|
|
|
107
114
|
|
|
@@ -109,6 +116,8 @@ class UserUpdateGroups(BaseModel):
|
|
|
109
116
|
"""
|
|
110
117
|
Schema for `POST /auth/users/{user_id}/set-groups/`
|
|
111
118
|
|
|
119
|
+
Attributes:
|
|
120
|
+
group_ids:
|
|
112
121
|
"""
|
|
113
122
|
|
|
114
123
|
model_config = ConfigDict(extra="forbid")
|
|
@@ -117,6 +126,14 @@ class UserUpdateGroups(BaseModel):
|
|
|
117
126
|
|
|
118
127
|
|
|
119
128
|
class UserProfileInfo(BaseModel):
|
|
129
|
+
"""
|
|
130
|
+
Attributes:
|
|
131
|
+
has_profile:
|
|
132
|
+
resource_name:
|
|
133
|
+
profile_name:
|
|
134
|
+
username:
|
|
135
|
+
"""
|
|
136
|
+
|
|
120
137
|
has_profile: bool
|
|
121
138
|
resource_name: str | None = None
|
|
122
139
|
profile_name: str | None = None
|
|
@@ -2,16 +2,13 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
from pydantic import ConfigDict
|
|
5
|
-
from pydantic import Field
|
|
6
5
|
from pydantic import field_serializer
|
|
7
6
|
from pydantic.types import AwareDatetime
|
|
8
7
|
|
|
9
|
-
from fractal_server.types import ListUniqueAbsolutePathStr
|
|
10
8
|
from fractal_server.types import NonEmptyStr
|
|
11
9
|
|
|
12
10
|
__all__ = (
|
|
13
11
|
"UserGroupRead",
|
|
14
|
-
"UserGroupUpdate",
|
|
15
12
|
"UserGroupCreate",
|
|
16
13
|
)
|
|
17
14
|
|
|
@@ -34,7 +31,6 @@ class UserGroupRead(BaseModel):
|
|
|
34
31
|
name: str
|
|
35
32
|
timestamp_created: AwareDatetime
|
|
36
33
|
user_ids: list[int] | None = None
|
|
37
|
-
viewer_paths: list[str]
|
|
38
34
|
|
|
39
35
|
@field_serializer("timestamp_created")
|
|
40
36
|
def serialize_datetime(v: datetime) -> str:
|
|
@@ -52,14 +48,3 @@ class UserGroupCreate(BaseModel):
|
|
|
52
48
|
model_config = ConfigDict(extra="forbid")
|
|
53
49
|
|
|
54
50
|
name: NonEmptyStr
|
|
55
|
-
viewer_paths: ListUniqueAbsolutePathStr = Field(default_factory=list)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class UserGroupUpdate(BaseModel):
|
|
59
|
-
"""
|
|
60
|
-
Schema for `UserGroup` update
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
model_config = ConfigDict(extra="forbid")
|
|
64
|
-
|
|
65
|
-
viewer_paths: ListUniqueAbsolutePathStr = None
|