fractal-server 2.7.0a5__tar.gz → 2.7.0a7__tar.gz
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-2.7.0a5 → fractal_server-2.7.0a7}/PKG-INFO +1 -1
- fractal_server-2.7.0a7/fractal_server/__init__.py +1 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/__init__.py +4 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/_aux_functions_tasks.py +72 -11
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/workflow.py +14 -82
- fractal_server-2.7.0a7/fractal_server/app/routes/api/v2/workflow_import.py +357 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/auth/group.py +27 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/__init__.py +13 -7
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/__init__.py +1 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/manifest.py +13 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/task.py +20 -5
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/workflowtask.py +3 -1
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/data_migrations/2_7_0.py +62 -3
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/pyproject.toml +2 -2
- fractal_server-2.7.0a5/fractal_server/__init__.py +0 -1
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/LICENSE +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/README.md +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/__main__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/alembic.ini +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/db/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/linkusergroup.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/linkuserproject.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/security.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/user_settings.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v1/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v1/dataset.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v1/job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v1/project.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v1/state.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v1/task.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v1/workflow.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v2/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v2/collection_state.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v2/dataset.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v2/job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v2/project.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v2/task.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v2/workflow.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/models/v2/workflowtask.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/admin/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/admin/v1.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/admin/v2/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/admin/v2/job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/admin/v2/project.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/admin/v2/task.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/admin/v2/task_group.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v1/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v1/_aux_functions.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v1/dataset.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v1/job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v1/project.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v1/task.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v1/task_collection.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v1/workflow.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v1/workflowtask.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/_aux_functions.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/dataset.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/images.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/project.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/status.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/submit.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/task.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/task_collection.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/task_collection_custom.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/task_group.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/workflowtask.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/auth/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/auth/_aux_auth.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/auth/current_user.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/auth/login.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/auth/oauth.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/auth/register.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/auth/router.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/auth/users.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/aux/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/aux/_job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/aux/_runner.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/aux/validate_user_settings.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/.gitignore +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/async_wrap.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/components.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/compress_folder.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/exceptions.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/_batching.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/_slurm_config.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/remote.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/ssh/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/ssh/executor.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/sudo/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/sudo/_subprocess_run_as_user.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/executors/slurm/sudo/executor.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/extract_archive.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/filenames.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/run_subprocess.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/set_start_and_last_task_index.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/shutdown.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/task_files.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/_common.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/_local/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/_local/_local_config.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/_local/_submit_setup.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/_local/executor.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/_slurm/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/_slurm/_submit_setup.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/_slurm/get_slurm_config.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/common.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v1/handle_failed_job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_local/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_local/_local_config.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_local/_submit_setup.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_local/executor.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_local_experimental/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_local_experimental/_local_config.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_local_experimental/_submit_setup.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_local_experimental/executor.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_slurm_common/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_slurm_ssh/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_slurm_ssh/_submit_setup.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_slurm_sudo/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/_slurm_sudo/_submit_setup.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/deduplicate_list.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/handle_failed_job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/merge_outputs.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/runner.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/runner_functions.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/runner_functions_low_level.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/v2/task_interface.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/runner/versions.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/_validators.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/user.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/user_group.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/user_settings.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/applyworkflow.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/dataset.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/dumps.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/manifest.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/project.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/state.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/task.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/task_collection.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v1/workflow.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/dataset.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/dumps.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/job.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/project.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/status.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/task_collection.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/task_group.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/schemas/v2/workflow.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/security/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/user_settings.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/config.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/data_migrations/README.md +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/data_migrations/tools.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/gunicorn_fractal.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/images/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/images/models.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/images/tools.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/logger.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/main.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/README +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/env.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/naming_convention.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/script.py.mako +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/034a469ec2eb_task_groups.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/py.typed +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/ssh/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/ssh/_fabric.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/string_tools.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/syringe.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/utils.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v1/_TaskCollectPip.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v1/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v1/background_operations.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v1/endpoint_operations.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v1/get_collection_data.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v1/utils.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/__init__.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/_venv_pip.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/background_operations.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/background_operations_ssh.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/database_operations.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/endpoint_operations.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/templates/_1_create_venv.sh +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/templates/_2_upgrade_pip.sh +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/templates/_3_pip_install.sh +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/templates/_4_pip_freeze.sh +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/templates/_5_pip_show.sh +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/tasks/v2/utils.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/urls.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/utils.py +0 -0
- {fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/zip_tools.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
__VERSION__ = "2.7.0a7"
|
{fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/__init__.py
RENAMED
@@ -14,6 +14,7 @@ from .task_collection import router as task_collection_router_v2
|
|
14
14
|
from .task_collection_custom import router as task_collection_router_v2_custom
|
15
15
|
from .task_group import router as task_group_router_v2
|
16
16
|
from .workflow import router as workflow_router_v2
|
17
|
+
from .workflow_import import router as workflow_import_router_v2
|
17
18
|
from .workflowtask import router as workflowtask_router_v2
|
18
19
|
from fractal_server.config import get_settings
|
19
20
|
from fractal_server.syringe import Inject
|
@@ -42,5 +43,8 @@ router_api_v2.include_router(
|
|
42
43
|
task_group_router_v2, prefix="/task-group", tags=["V2 TaskGroup"]
|
43
44
|
)
|
44
45
|
router_api_v2.include_router(workflow_router_v2, tags=["V2 Workflow"])
|
46
|
+
router_api_v2.include_router(
|
47
|
+
workflow_import_router_v2, tags=["V2 Workflow Import"]
|
48
|
+
)
|
45
49
|
router_api_v2.include_router(workflowtask_router_v2, tags=["V2 WorkflowTask"])
|
46
50
|
router_api_v2.include_router(status_router_v2, tags=["V2 Status"])
|
@@ -9,13 +9,21 @@ from fastapi import HTTPException
|
|
9
9
|
from fastapi import status
|
10
10
|
from sqlmodel import select
|
11
11
|
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
12
|
+
from fractal_server.app.db import AsyncSession
|
13
|
+
from fractal_server.app.models import LinkUserGroup
|
14
|
+
from fractal_server.app.models import UserGroup
|
15
|
+
from fractal_server.app.models import UserOAuth
|
16
|
+
from fractal_server.app.models.v2 import CollectionStateV2
|
17
|
+
from fractal_server.app.models.v2 import TaskGroupV2
|
18
|
+
from fractal_server.app.models.v2 import TaskV2
|
19
|
+
from fractal_server.app.models.v2 import WorkflowTaskV2
|
20
|
+
from fractal_server.app.routes.auth._aux_auth import _get_default_usergroup_id
|
21
|
+
from fractal_server.app.routes.auth._aux_auth import (
|
22
|
+
_verify_user_belongs_to_group,
|
23
|
+
)
|
24
|
+
from fractal_server.logger import set_logger
|
25
|
+
|
26
|
+
logger = set_logger(__name__)
|
19
27
|
|
20
28
|
|
21
29
|
async def _get_task_group_or_404(
|
@@ -211,6 +219,33 @@ async def _get_valid_user_group_id(
|
|
211
219
|
return user_group_id
|
212
220
|
|
213
221
|
|
222
|
+
async def _get_collection_status_message(
|
223
|
+
task_group: TaskGroupV2, db: AsyncSession
|
224
|
+
) -> str:
|
225
|
+
res = await db.execute(
|
226
|
+
select(CollectionStateV2).where(
|
227
|
+
CollectionStateV2.taskgroupv2_id == task_group.id
|
228
|
+
)
|
229
|
+
)
|
230
|
+
states = res.scalars().all()
|
231
|
+
if len(states) > 1:
|
232
|
+
msg = (
|
233
|
+
"Expected one CollectionStateV2 associated to TaskGroup "
|
234
|
+
f"{task_group.id}, found {len(states)} "
|
235
|
+
f"(IDs: {[state.id for state in states]}).\n"
|
236
|
+
"Warning: this should have not happened, please contact an admin."
|
237
|
+
)
|
238
|
+
elif len(states) == 1:
|
239
|
+
msg = (
|
240
|
+
f"\nThere exists a task-collection state (ID={states[0].id}) for "
|
241
|
+
f"such task group (ID={task_group.id}), with status "
|
242
|
+
f"'{states[0].data.get('status')}'."
|
243
|
+
)
|
244
|
+
else:
|
245
|
+
msg = ""
|
246
|
+
return msg
|
247
|
+
|
248
|
+
|
214
249
|
async def _verify_non_duplication_user_constraint(
|
215
250
|
db: AsyncSession,
|
216
251
|
user_id: int,
|
@@ -226,11 +261,24 @@ async def _verify_non_duplication_user_constraint(
|
|
226
261
|
res = await db.execute(stm)
|
227
262
|
duplicate = res.scalars().all()
|
228
263
|
if duplicate:
|
264
|
+
user = await db.get(UserOAuth, user_id)
|
265
|
+
if len(duplicate) > 1:
|
266
|
+
raise HTTPException(
|
267
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
268
|
+
detail=(
|
269
|
+
"Invalid state:\n"
|
270
|
+
f"User '{user.email}' already owns {len(duplicate)} task "
|
271
|
+
f"groups with name='{pkg_name}' and {version=} "
|
272
|
+
f"(IDs: {[group.id for group in duplicate]}).\n"
|
273
|
+
"This should have not happened: please contact an admin."
|
274
|
+
),
|
275
|
+
)
|
276
|
+
state_msg = await _get_collection_status_message(duplicate[0], db)
|
229
277
|
raise HTTPException(
|
230
278
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
231
279
|
detail=(
|
232
|
-
"
|
233
|
-
f"
|
280
|
+
f"User '{user.email}' already owns a task group "
|
281
|
+
f"with name='{pkg_name}' and {version=}.{state_msg}"
|
234
282
|
),
|
235
283
|
)
|
236
284
|
|
@@ -253,11 +301,24 @@ async def _verify_non_duplication_group_constraint(
|
|
253
301
|
res = await db.execute(stm)
|
254
302
|
duplicate = res.scalars().all()
|
255
303
|
if duplicate:
|
304
|
+
user_group = await db.get(UserGroup, user_group_id)
|
305
|
+
if len(duplicate) > 1:
|
306
|
+
raise HTTPException(
|
307
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
308
|
+
detail=(
|
309
|
+
"Invalid state:\n"
|
310
|
+
f"UserGroup '{user_group.name}' already owns "
|
311
|
+
f"{len(duplicate)} task groups with name='{pkg_name}' and "
|
312
|
+
f"{version=} (IDs: {[group.id for group in duplicate]}).\n"
|
313
|
+
"This should have not happened: please contact an admin."
|
314
|
+
),
|
315
|
+
)
|
316
|
+
state_msg = await _get_collection_status_message(duplicate[0], db)
|
256
317
|
raise HTTPException(
|
257
318
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
258
319
|
detail=(
|
259
|
-
"
|
260
|
-
f"
|
320
|
+
f"UserGroup {user_group.name} already owns a task group "
|
321
|
+
f"with {pkg_name=} and {version=}.{state_msg}"
|
261
322
|
),
|
262
323
|
)
|
263
324
|
|
{fractal_server-2.7.0a5 → fractal_server-2.7.0a7}/fractal_server/app/routes/api/v2/workflow.py
RENAMED
@@ -11,25 +11,21 @@ from ....db import AsyncSession
|
|
11
11
|
from ....db import get_async_db
|
12
12
|
from ....models.v2 import JobV2
|
13
13
|
from ....models.v2 import ProjectV2
|
14
|
-
from ....models.v2 import TaskV2
|
15
14
|
from ....models.v2 import WorkflowV2
|
16
15
|
from ....schemas.v2 import WorkflowCreateV2
|
17
16
|
from ....schemas.v2 import WorkflowExportV2
|
18
|
-
from ....schemas.v2 import WorkflowImportV2
|
19
17
|
from ....schemas.v2 import WorkflowReadV2
|
20
18
|
from ....schemas.v2 import WorkflowReadV2WithWarnings
|
21
|
-
from ....schemas.v2 import WorkflowTaskCreateV2
|
22
19
|
from ....schemas.v2 import WorkflowUpdateV2
|
23
20
|
from ._aux_functions import _check_workflow_exists
|
24
21
|
from ._aux_functions import _get_project_check_owner
|
25
22
|
from ._aux_functions import _get_submitted_jobs_statement
|
26
23
|
from ._aux_functions import _get_workflow_check_owner
|
27
|
-
from ._aux_functions import _workflow_insert_task
|
28
24
|
from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
|
29
25
|
from fractal_server.app.models import UserOAuth
|
26
|
+
from fractal_server.app.models.v2.task import TaskGroupV2
|
30
27
|
from fractal_server.app.routes.auth import current_active_user
|
31
28
|
|
32
|
-
|
33
29
|
router = APIRouter()
|
34
30
|
|
35
31
|
|
@@ -256,85 +252,21 @@ async def export_worfklow(
|
|
256
252
|
user_id=user.id,
|
257
253
|
db=db,
|
258
254
|
)
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
async def import_workflow(
|
268
|
-
project_id: int,
|
269
|
-
workflow: WorkflowImportV2,
|
270
|
-
user: UserOAuth = Depends(current_active_user),
|
271
|
-
db: AsyncSession = Depends(get_async_db),
|
272
|
-
) -> Optional[WorkflowReadV2]:
|
273
|
-
"""
|
274
|
-
Import an existing workflow into a project
|
275
|
-
|
276
|
-
Also create all required objects (i.e. Workflow and WorkflowTask's) along
|
277
|
-
the way.
|
278
|
-
"""
|
279
|
-
|
280
|
-
# Preliminary checks
|
281
|
-
await _get_project_check_owner(
|
282
|
-
project_id=project_id,
|
283
|
-
user_id=user.id,
|
284
|
-
db=db,
|
285
|
-
)
|
286
|
-
|
287
|
-
await _check_workflow_exists(
|
288
|
-
name=workflow.name, project_id=project_id, db=db
|
289
|
-
)
|
290
|
-
|
291
|
-
# Check that all required tasks are available
|
292
|
-
source_to_id = {}
|
293
|
-
|
294
|
-
for wf_task in workflow.task_list:
|
295
|
-
|
296
|
-
source = wf_task.task.source
|
297
|
-
if source not in source_to_id.keys():
|
298
|
-
stm = select(TaskV2).where(TaskV2.source == source)
|
299
|
-
tasks_by_source = (await db.execute(stm)).scalars().all()
|
300
|
-
if len(tasks_by_source) != 1:
|
301
|
-
raise HTTPException(
|
302
|
-
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
303
|
-
detail=(
|
304
|
-
f"Found {len(tasks_by_source)} tasks "
|
305
|
-
f"with {source=}."
|
306
|
-
),
|
307
|
-
)
|
308
|
-
source_to_id[source] = tasks_by_source[0].id
|
309
|
-
|
310
|
-
# Create new Workflow (with empty task_list)
|
311
|
-
db_workflow = WorkflowV2(
|
312
|
-
project_id=project_id,
|
313
|
-
**workflow.dict(exclude_none=True, exclude={"task_list"}),
|
314
|
-
)
|
315
|
-
db.add(db_workflow)
|
316
|
-
await db.commit()
|
317
|
-
await db.refresh(db_workflow)
|
318
|
-
|
319
|
-
# Insert tasks
|
320
|
-
|
321
|
-
for wf_task in workflow.task_list:
|
322
|
-
source = wf_task.task.source
|
323
|
-
task_id = source_to_id[source]
|
324
|
-
|
325
|
-
new_wf_task = WorkflowTaskCreateV2(
|
326
|
-
**wf_task.dict(exclude_none=True, exclude={"task"})
|
327
|
-
)
|
328
|
-
# Insert task
|
329
|
-
await _workflow_insert_task(
|
330
|
-
**new_wf_task.dict(),
|
331
|
-
workflow_id=db_workflow.id,
|
332
|
-
task_id=task_id,
|
333
|
-
db=db,
|
255
|
+
wf_task_list = []
|
256
|
+
for wftask in workflow.task_list:
|
257
|
+
task_group = await db.get(TaskGroupV2, wftask.task.taskgroupv2_id)
|
258
|
+
wf_task_list.append(wftask.dict())
|
259
|
+
wf_task_list[-1]["task"] = dict(
|
260
|
+
pkg_name=task_group.pkg_name,
|
261
|
+
version=task_group.version,
|
262
|
+
name=wftask.task.name,
|
334
263
|
)
|
335
264
|
|
336
|
-
|
337
|
-
|
265
|
+
wf = WorkflowExportV2(
|
266
|
+
**workflow.model_dump(),
|
267
|
+
task_list=wf_task_list,
|
268
|
+
)
|
269
|
+
return wf
|
338
270
|
|
339
271
|
|
340
272
|
@router.get("/workflow/", response_model=list[WorkflowReadV2])
|
@@ -0,0 +1,357 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from fastapi import APIRouter
|
4
|
+
from fastapi import Depends
|
5
|
+
from fastapi import HTTPException
|
6
|
+
from fastapi import status
|
7
|
+
from sqlmodel import or_
|
8
|
+
from sqlmodel import select
|
9
|
+
|
10
|
+
from ....db import AsyncSession
|
11
|
+
from ....db import get_async_db
|
12
|
+
from ....models.v2 import TaskV2
|
13
|
+
from ....models.v2 import WorkflowV2
|
14
|
+
from ....schemas.v2 import TaskImportV2Legacy
|
15
|
+
from ....schemas.v2 import WorkflowImportV2
|
16
|
+
from ....schemas.v2 import WorkflowReadV2WithWarnings
|
17
|
+
from ....schemas.v2 import WorkflowTaskCreateV2
|
18
|
+
from ._aux_functions import _check_workflow_exists
|
19
|
+
from ._aux_functions import _get_project_check_owner
|
20
|
+
from ._aux_functions import _workflow_insert_task
|
21
|
+
from ._aux_functions_tasks import _add_warnings_to_workflow_tasks
|
22
|
+
from fractal_server.app.models import LinkUserGroup
|
23
|
+
from fractal_server.app.models import UserOAuth
|
24
|
+
from fractal_server.app.models.v2.task import TaskGroupV2
|
25
|
+
from fractal_server.app.routes.auth import current_active_user
|
26
|
+
from fractal_server.app.routes.auth._aux_auth import _get_default_usergroup_id
|
27
|
+
from fractal_server.app.schemas.v2.task import TaskImportV2
|
28
|
+
from fractal_server.logger import set_logger
|
29
|
+
|
30
|
+
router = APIRouter()
|
31
|
+
|
32
|
+
|
33
|
+
logger = set_logger(__name__)
|
34
|
+
|
35
|
+
|
36
|
+
async def _get_user_accessible_taskgroups(
|
37
|
+
*,
|
38
|
+
user_id: int,
|
39
|
+
db: AsyncSession,
|
40
|
+
) -> list[TaskGroupV2]:
|
41
|
+
"""
|
42
|
+
Retrieve list of task groups that the user has access to.
|
43
|
+
"""
|
44
|
+
stm = select(TaskGroupV2).where(
|
45
|
+
or_(
|
46
|
+
TaskGroupV2.user_id == user_id,
|
47
|
+
TaskGroupV2.user_group_id.in_(
|
48
|
+
select(LinkUserGroup.group_id).where(
|
49
|
+
LinkUserGroup.user_id == user_id
|
50
|
+
)
|
51
|
+
),
|
52
|
+
)
|
53
|
+
)
|
54
|
+
res = await db.execute(stm)
|
55
|
+
accessible_task_groups = res.scalars().all()
|
56
|
+
logger.info(
|
57
|
+
f"Found {len(accessible_task_groups)} accessible "
|
58
|
+
f"task groups for {user_id=}."
|
59
|
+
)
|
60
|
+
return accessible_task_groups
|
61
|
+
|
62
|
+
|
63
|
+
async def _get_task_by_source(
|
64
|
+
source: str,
|
65
|
+
task_groups_list: list[TaskGroupV2],
|
66
|
+
) -> Optional[int]:
|
67
|
+
"""
|
68
|
+
Find task with a given source.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
task_import: Info on task to be imported.
|
72
|
+
user_id: ID of current user.
|
73
|
+
default_group_id: ID of default user group.
|
74
|
+
task_group_list: Current list of valid task groups.
|
75
|
+
db: Asynchronous db session
|
76
|
+
|
77
|
+
Return:
|
78
|
+
`id` of the matching task, or `None`.
|
79
|
+
"""
|
80
|
+
task_id = next(
|
81
|
+
iter(
|
82
|
+
task.id
|
83
|
+
for task_group in task_groups_list
|
84
|
+
for task in task_group.task_list
|
85
|
+
if task.source == source
|
86
|
+
),
|
87
|
+
None,
|
88
|
+
)
|
89
|
+
return task_id
|
90
|
+
|
91
|
+
|
92
|
+
async def _disambiguate_task_groups(
|
93
|
+
*,
|
94
|
+
matching_task_groups: list[TaskGroupV2],
|
95
|
+
user_id: int,
|
96
|
+
db: AsyncSession,
|
97
|
+
default_group_id: int,
|
98
|
+
) -> Optional[TaskV2]:
|
99
|
+
"""
|
100
|
+
Disambiguate task groups based on ownership information.
|
101
|
+
"""
|
102
|
+
# Highest priority: task groups created by user
|
103
|
+
for task_group in matching_task_groups:
|
104
|
+
if task_group.user_id == user_id:
|
105
|
+
logger.info(
|
106
|
+
"[_disambiguate_task_groups] "
|
107
|
+
f"Found task group {task_group.id} with {user_id=}, return."
|
108
|
+
)
|
109
|
+
return task_group
|
110
|
+
logger.info(
|
111
|
+
"[_disambiguate_task_groups] "
|
112
|
+
f"No task group found with {user_id=}, continue."
|
113
|
+
)
|
114
|
+
|
115
|
+
# Medium priority: task groups owned by default user group
|
116
|
+
for task_group in matching_task_groups:
|
117
|
+
if task_group.user_group_id == default_group_id:
|
118
|
+
logger.info(
|
119
|
+
"[_disambiguate_task_groups] "
|
120
|
+
f"Found task group {task_group.id} with user_group_id="
|
121
|
+
f"{default_group_id}, return."
|
122
|
+
)
|
123
|
+
return task_group
|
124
|
+
logger.info(
|
125
|
+
"[_disambiguate_task_groups] "
|
126
|
+
"No task group found with user_group_id="
|
127
|
+
f"{default_group_id}, continue."
|
128
|
+
)
|
129
|
+
|
130
|
+
# Lowest priority: task groups owned by other groups, sorted
|
131
|
+
# according to age of the user/usergroup link
|
132
|
+
logger.info(
|
133
|
+
"[_disambiguate_task_groups] "
|
134
|
+
"Now sorting remaining task groups by oldest-user-link."
|
135
|
+
)
|
136
|
+
user_group_ids = [
|
137
|
+
task_group.user_group_id for task_group in matching_task_groups
|
138
|
+
]
|
139
|
+
stm = (
|
140
|
+
select(LinkUserGroup.group_id)
|
141
|
+
.where(LinkUserGroup.user_id == user_id)
|
142
|
+
.where(LinkUserGroup.group_id.in_(user_group_ids))
|
143
|
+
.order_by(LinkUserGroup.timestamp_created.asc())
|
144
|
+
)
|
145
|
+
res = await db.execute(stm)
|
146
|
+
oldest_user_group_id = res.scalars().first()
|
147
|
+
logger.info(
|
148
|
+
"[_disambiguate_task_groups] "
|
149
|
+
f"Result of sorting: {oldest_user_group_id=}."
|
150
|
+
)
|
151
|
+
task_group = next(
|
152
|
+
iter(
|
153
|
+
task_group
|
154
|
+
for task_group in matching_task_groups
|
155
|
+
if task_group.user_group_id == oldest_user_group_id
|
156
|
+
),
|
157
|
+
None,
|
158
|
+
)
|
159
|
+
return task_group
|
160
|
+
|
161
|
+
|
162
|
+
async def _get_task_by_taskimport(
|
163
|
+
*,
|
164
|
+
task_import: TaskImportV2,
|
165
|
+
task_groups_list: list[TaskGroupV2],
|
166
|
+
user_id: int,
|
167
|
+
default_group_id: int,
|
168
|
+
db: AsyncSession,
|
169
|
+
) -> Optional[int]:
|
170
|
+
"""
|
171
|
+
Find a task based on `task_import`.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
task_import: Info on task to be imported.
|
175
|
+
user_id: ID of current user.
|
176
|
+
default_group_id: ID of default user group.
|
177
|
+
task_group_list: Current list of valid task groups.
|
178
|
+
db: Asynchronous db session
|
179
|
+
|
180
|
+
Return:
|
181
|
+
`id` of the matching task, or `None`.
|
182
|
+
"""
|
183
|
+
|
184
|
+
logger.info(f"[_get_task_by_taskimport] START, {task_import=}")
|
185
|
+
|
186
|
+
# Filter by `pkg_name` and by presence of a task with given `name`.
|
187
|
+
matching_task_groups = [
|
188
|
+
task_group
|
189
|
+
for task_group in task_groups_list
|
190
|
+
if (
|
191
|
+
task_group.pkg_name == task_import.pkg_name
|
192
|
+
and task_import.name
|
193
|
+
in [task.name for task in task_group.task_list]
|
194
|
+
)
|
195
|
+
]
|
196
|
+
if len(matching_task_groups) < 1:
|
197
|
+
logger.info(
|
198
|
+
"[_get_task_by_taskimport] "
|
199
|
+
f"No task group with {task_import.pkg_name=} "
|
200
|
+
f"and a task with {task_import.name=}."
|
201
|
+
)
|
202
|
+
return None
|
203
|
+
|
204
|
+
# Determine target `version`
|
205
|
+
# Note that task_import.version cannot be "", due to a validator
|
206
|
+
if task_import.version is None:
|
207
|
+
logger.info(
|
208
|
+
"[_get_task_by_taskimport] "
|
209
|
+
"No version requested, looking for latest."
|
210
|
+
)
|
211
|
+
latest_task = max(
|
212
|
+
matching_task_groups, key=lambda tg: tg.version or ""
|
213
|
+
)
|
214
|
+
version = latest_task.version
|
215
|
+
logger.info(
|
216
|
+
f"[_get_task_by_taskimport] Latest version set to {version}."
|
217
|
+
)
|
218
|
+
else:
|
219
|
+
version = task_import.version
|
220
|
+
|
221
|
+
# Filter task groups by version
|
222
|
+
final_matching_task_groups = list(
|
223
|
+
filter(lambda tg: tg.version == version, task_groups_list)
|
224
|
+
)
|
225
|
+
|
226
|
+
if len(final_matching_task_groups) < 1:
|
227
|
+
logger.info(
|
228
|
+
"[_get_task_by_taskimport] "
|
229
|
+
"No task group left after filtering by version."
|
230
|
+
)
|
231
|
+
return None
|
232
|
+
elif len(final_matching_task_groups) == 1:
|
233
|
+
final_task_group = final_matching_task_groups[0]
|
234
|
+
logger.info(
|
235
|
+
"[_get_task_by_taskimport] "
|
236
|
+
"Found a single task group, after filtering by version."
|
237
|
+
)
|
238
|
+
else:
|
239
|
+
logger.info(
|
240
|
+
"[_get_task_by_taskimport] "
|
241
|
+
"Found many task groups, after filtering by version."
|
242
|
+
)
|
243
|
+
final_task_group = await _disambiguate_task_groups(
|
244
|
+
matching_task_groups, user_id, db, default_group_id
|
245
|
+
)
|
246
|
+
if final_task_group is None:
|
247
|
+
logger.info(
|
248
|
+
"[_get_task_by_taskimport] Disambiguation returned None."
|
249
|
+
)
|
250
|
+
return None
|
251
|
+
|
252
|
+
# Find task with given name
|
253
|
+
task_id = next(
|
254
|
+
iter(
|
255
|
+
task.id
|
256
|
+
for task in final_task_group.task_list
|
257
|
+
if task.name == task_import.name
|
258
|
+
),
|
259
|
+
None,
|
260
|
+
)
|
261
|
+
|
262
|
+
logger.info(f"[_get_task_by_taskimport] END, {task_import=}, {task_id=}.")
|
263
|
+
|
264
|
+
return task_id
|
265
|
+
|
266
|
+
|
267
|
+
@router.post(
|
268
|
+
"/project/{project_id}/workflow/import/",
|
269
|
+
response_model=WorkflowReadV2WithWarnings,
|
270
|
+
status_code=status.HTTP_201_CREATED,
|
271
|
+
)
|
272
|
+
async def import_workflow(
|
273
|
+
project_id: int,
|
274
|
+
workflow_import: WorkflowImportV2,
|
275
|
+
user: UserOAuth = Depends(current_active_user),
|
276
|
+
db: AsyncSession = Depends(get_async_db),
|
277
|
+
) -> WorkflowReadV2WithWarnings:
|
278
|
+
"""
|
279
|
+
Import an existing workflow into a project and create required objects.
|
280
|
+
"""
|
281
|
+
|
282
|
+
# Preliminary checks
|
283
|
+
await _get_project_check_owner(
|
284
|
+
project_id=project_id,
|
285
|
+
user_id=user.id,
|
286
|
+
db=db,
|
287
|
+
)
|
288
|
+
await _check_workflow_exists(
|
289
|
+
name=workflow_import.name,
|
290
|
+
project_id=project_id,
|
291
|
+
db=db,
|
292
|
+
)
|
293
|
+
|
294
|
+
task_group_list = await _get_user_accessible_taskgroups(
|
295
|
+
user_id=user.id,
|
296
|
+
db=db,
|
297
|
+
)
|
298
|
+
default_group_id = await _get_default_usergroup_id(db)
|
299
|
+
|
300
|
+
list_wf_tasks = []
|
301
|
+
list_task_ids = []
|
302
|
+
for wf_task in workflow_import.task_list:
|
303
|
+
task_import = wf_task.task
|
304
|
+
if isinstance(task_import, TaskImportV2Legacy):
|
305
|
+
task_id = await _get_task_by_source(
|
306
|
+
source=task_import.source,
|
307
|
+
task_groups_list=task_group_list,
|
308
|
+
)
|
309
|
+
else:
|
310
|
+
task_id = await _get_task_by_taskimport(
|
311
|
+
task_import=task_import,
|
312
|
+
user_id=user.id,
|
313
|
+
default_group_id=default_group_id,
|
314
|
+
task_groups_list=task_group_list,
|
315
|
+
db=db,
|
316
|
+
)
|
317
|
+
if task_id is None:
|
318
|
+
raise HTTPException(
|
319
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
320
|
+
detail=f"Could not find a task matching with {wf_task.task}.",
|
321
|
+
)
|
322
|
+
new_wf_task = WorkflowTaskCreateV2(
|
323
|
+
**wf_task.dict(exclude_none=True, exclude={"task"})
|
324
|
+
)
|
325
|
+
list_wf_tasks.append(new_wf_task)
|
326
|
+
list_task_ids.append(task_id)
|
327
|
+
|
328
|
+
# Create new Workflow
|
329
|
+
db_workflow = WorkflowV2(
|
330
|
+
project_id=project_id,
|
331
|
+
**workflow_import.dict(exclude_none=True, exclude={"task_list"}),
|
332
|
+
)
|
333
|
+
db.add(db_workflow)
|
334
|
+
await db.commit()
|
335
|
+
await db.refresh(db_workflow)
|
336
|
+
|
337
|
+
# Insert task into the workflow
|
338
|
+
for ind, new_wf_task in enumerate(list_wf_tasks):
|
339
|
+
await _workflow_insert_task(
|
340
|
+
**new_wf_task.dict(),
|
341
|
+
workflow_id=db_workflow.id,
|
342
|
+
task_id=list_task_ids[ind],
|
343
|
+
db=db,
|
344
|
+
)
|
345
|
+
|
346
|
+
# Add warnings for non-active tasks (or non-accessible tasks,
|
347
|
+
# although that should never happen)
|
348
|
+
wftask_list_with_warnings = await _add_warnings_to_workflow_tasks(
|
349
|
+
wftask_list=db_workflow.task_list, user_id=user.id, db=db
|
350
|
+
)
|
351
|
+
workflow_data = dict(
|
352
|
+
**db_workflow.model_dump(),
|
353
|
+
project=db_workflow.project,
|
354
|
+
task_list=wftask_list_with_warnings,
|
355
|
+
)
|
356
|
+
|
357
|
+
return workflow_data
|
@@ -19,10 +19,12 @@ from fractal_server.app.db import get_async_db
|
|
19
19
|
from fractal_server.app.models import LinkUserGroup
|
20
20
|
from fractal_server.app.models import UserGroup
|
21
21
|
from fractal_server.app.models import UserOAuth
|
22
|
+
from fractal_server.app.models import UserSettings
|
22
23
|
from fractal_server.app.models.v2 import TaskGroupV2
|
23
24
|
from fractal_server.app.schemas.user_group import UserGroupCreate
|
24
25
|
from fractal_server.app.schemas.user_group import UserGroupRead
|
25
26
|
from fractal_server.app.schemas.user_group import UserGroupUpdate
|
27
|
+
from fractal_server.app.schemas.user_settings import UserSettingsUpdate
|
26
28
|
from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
|
27
29
|
from fractal_server.logger import set_logger
|
28
30
|
|
@@ -212,3 +214,28 @@ async def delete_single_group(
|
|
212
214
|
await db.commit()
|
213
215
|
|
214
216
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
217
|
+
|
218
|
+
|
219
|
+
@router_group.patch("/group/{group_id}/user-settings/", status_code=200)
|
220
|
+
async def patch_user_settings_bulk(
|
221
|
+
group_id: int,
|
222
|
+
settings_update: UserSettingsUpdate,
|
223
|
+
superuser: UserOAuth = Depends(current_active_superuser),
|
224
|
+
db: AsyncSession = Depends(get_async_db),
|
225
|
+
):
|
226
|
+
await _usergroup_or_404(group_id, db)
|
227
|
+
res = await db.execute(
|
228
|
+
select(UserSettings)
|
229
|
+
.join(UserOAuth)
|
230
|
+
.where(LinkUserGroup.user_id == UserOAuth.id)
|
231
|
+
.where(LinkUserGroup.group_id == group_id)
|
232
|
+
)
|
233
|
+
settings_list = res.scalars().all()
|
234
|
+
update = settings_update.dict(exclude_unset=True)
|
235
|
+
for settings in settings_list:
|
236
|
+
for k, v in update.items():
|
237
|
+
setattr(settings, k, v)
|
238
|
+
db.add(settings)
|
239
|
+
await db.commit()
|
240
|
+
|
241
|
+
return Response(status_code=status.HTTP_200_OK)
|