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
|
@@ -2,29 +2,30 @@ import time
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from tempfile import TemporaryDirectory
|
|
4
4
|
|
|
5
|
-
from ..utils_background import add_commit_refresh
|
|
6
|
-
from ..utils_background import fail_and_cleanup
|
|
7
|
-
from ..utils_background import get_activity_and_task_group
|
|
8
|
-
from ..utils_templates import get_collection_replacements
|
|
9
|
-
from ._utils import _customize_and_run_template
|
|
10
|
-
from ._utils import check_ssh_or_fail_and_cleanup
|
|
11
5
|
from fractal_server.app.db import get_sync_db
|
|
12
6
|
from fractal_server.app.models import Profile
|
|
13
7
|
from fractal_server.app.models import Resource
|
|
14
|
-
from fractal_server.app.schemas.v2 import
|
|
15
|
-
from fractal_server.app.schemas.v2.task_group import
|
|
8
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityAction
|
|
9
|
+
from fractal_server.app.schemas.v2.task_group import TaskGroupActivityStatus
|
|
16
10
|
from fractal_server.logger import reset_logger_handlers
|
|
17
11
|
from fractal_server.logger import set_logger
|
|
18
12
|
from fractal_server.ssh._fabric import SingleUseFractalSSH
|
|
19
13
|
from fractal_server.ssh._fabric import SSHConfig
|
|
20
14
|
from fractal_server.tasks.utils import get_log_path
|
|
15
|
+
from fractal_server.tasks.v2.utils_background import add_commit_refresh
|
|
16
|
+
from fractal_server.tasks.v2.utils_background import fail_and_cleanup
|
|
17
|
+
from fractal_server.tasks.v2.utils_background import get_activity_and_task_group
|
|
21
18
|
from fractal_server.tasks.v2.utils_background import get_current_log
|
|
22
19
|
from fractal_server.tasks.v2.utils_python_interpreter import (
|
|
23
20
|
get_python_interpreter,
|
|
24
21
|
)
|
|
25
22
|
from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
|
|
23
|
+
from fractal_server.tasks.v2.utils_templates import get_collection_replacements
|
|
26
24
|
from fractal_server.utils import get_timestamp
|
|
27
25
|
|
|
26
|
+
from ._utils import _customize_and_run_template
|
|
27
|
+
from ._utils import check_ssh_or_fail_and_cleanup
|
|
28
|
+
|
|
28
29
|
|
|
29
30
|
def reactivate_ssh(
|
|
30
31
|
*,
|
|
@@ -40,9 +41,10 @@ def reactivate_ssh(
|
|
|
40
41
|
handled.
|
|
41
42
|
|
|
42
43
|
Args:
|
|
43
|
-
task_group_id:
|
|
44
44
|
task_group_activity_id:
|
|
45
|
-
|
|
45
|
+
task_group_id:
|
|
46
|
+
resource:
|
|
47
|
+
profile:
|
|
46
48
|
"""
|
|
47
49
|
|
|
48
50
|
LOGGER_NAME = f"{__name__}.ID{task_group_activity_id}"
|
|
@@ -101,7 +103,7 @@ def reactivate_ssh(
|
|
|
101
103
|
)
|
|
102
104
|
return
|
|
103
105
|
|
|
104
|
-
activity.status =
|
|
106
|
+
activity.status = TaskGroupActivityStatus.ONGOING
|
|
105
107
|
activity = add_commit_refresh(obj=activity, db=db)
|
|
106
108
|
|
|
107
109
|
# Prepare replacements for templates
|
|
@@ -145,7 +147,7 @@ def reactivate_ssh(
|
|
|
145
147
|
script_dir_remote=script_dir_remote,
|
|
146
148
|
prefix=(
|
|
147
149
|
f"{int(time.time())}_"
|
|
148
|
-
f"{
|
|
150
|
+
f"{TaskGroupActivityAction.REACTIVATE}"
|
|
149
151
|
),
|
|
150
152
|
fractal_ssh=fractal_ssh,
|
|
151
153
|
logger_name=LOGGER_NAME,
|
|
@@ -170,7 +172,7 @@ def reactivate_ssh(
|
|
|
170
172
|
)
|
|
171
173
|
logger.info("end - install from pip freeze")
|
|
172
174
|
activity.log = get_current_log(log_file_path)
|
|
173
|
-
activity.status =
|
|
175
|
+
activity.status = TaskGroupActivityStatus.OK
|
|
174
176
|
activity.timestamp_ended = get_timestamp()
|
|
175
177
|
activity = add_commit_refresh(obj=activity, db=db)
|
|
176
178
|
task_group.active = True
|
|
@@ -182,9 +184,7 @@ def reactivate_ssh(
|
|
|
182
184
|
except Exception as reactivate_e:
|
|
183
185
|
# Delete corrupted venv_path
|
|
184
186
|
try:
|
|
185
|
-
logger.info(
|
|
186
|
-
f"Now delete folder {task_group.venv_path}"
|
|
187
|
-
)
|
|
187
|
+
logger.info(f"Now delete folder {task_group.venv_path}")
|
|
188
188
|
fractal_ssh.remove_folder(
|
|
189
189
|
folder=task_group.venv_path,
|
|
190
190
|
safe_root=profile.tasks_remote_dir,
|
|
@@ -2,17 +2,11 @@ import time
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from tempfile import TemporaryDirectory
|
|
4
4
|
|
|
5
|
-
from ..utils_background import fail_and_cleanup
|
|
6
|
-
from ..utils_background import get_activity_and_task_group
|
|
7
|
-
from ..utils_pixi import SOURCE_DIR_NAME
|
|
8
|
-
from ._pixi_slurm_ssh import run_script_on_remote_slurm
|
|
9
|
-
from ._utils import check_ssh_or_fail_and_cleanup
|
|
10
|
-
from ._utils import edit_pyproject_toml_in_place_ssh
|
|
11
5
|
from fractal_server.app.db import get_sync_db
|
|
12
6
|
from fractal_server.app.models import Profile
|
|
13
7
|
from fractal_server.app.models import Resource
|
|
14
|
-
from fractal_server.app.schemas.v2 import
|
|
15
|
-
from fractal_server.app.schemas.v2 import
|
|
8
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityAction
|
|
9
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityStatus
|
|
16
10
|
from fractal_server.logger import reset_logger_handlers
|
|
17
11
|
from fractal_server.logger import set_logger
|
|
18
12
|
from fractal_server.ssh._fabric import SingleUseFractalSSH
|
|
@@ -21,10 +15,17 @@ from fractal_server.tasks.utils import get_log_path
|
|
|
21
15
|
from fractal_server.tasks.v2.ssh._utils import _customize_and_run_template
|
|
22
16
|
from fractal_server.tasks.v2.ssh._utils import _customize_and_send_template
|
|
23
17
|
from fractal_server.tasks.v2.utils_background import add_commit_refresh
|
|
18
|
+
from fractal_server.tasks.v2.utils_background import fail_and_cleanup
|
|
19
|
+
from fractal_server.tasks.v2.utils_background import get_activity_and_task_group
|
|
24
20
|
from fractal_server.tasks.v2.utils_background import get_current_log
|
|
21
|
+
from fractal_server.tasks.v2.utils_pixi import SOURCE_DIR_NAME
|
|
25
22
|
from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
|
|
26
23
|
from fractal_server.utils import get_timestamp
|
|
27
24
|
|
|
25
|
+
from ._pixi_slurm_ssh import run_script_on_remote_slurm
|
|
26
|
+
from ._utils import check_ssh_or_fail_and_cleanup
|
|
27
|
+
from ._utils import edit_pyproject_toml_in_place_ssh
|
|
28
|
+
|
|
28
29
|
|
|
29
30
|
def reactivate_ssh_pixi(
|
|
30
31
|
*,
|
|
@@ -149,7 +150,7 @@ def reactivate_ssh_pixi(
|
|
|
149
150
|
logger.info("installing - START")
|
|
150
151
|
|
|
151
152
|
# Set status to ONGOING and refresh logs
|
|
152
|
-
activity.status =
|
|
153
|
+
activity.status = TaskGroupActivityStatus.ONGOING
|
|
153
154
|
activity.log = get_current_log(log_file_path)
|
|
154
155
|
activity = add_commit_refresh(obj=activity, db=db)
|
|
155
156
|
|
|
@@ -163,7 +164,7 @@ def reactivate_ssh_pixi(
|
|
|
163
164
|
script_dir_remote=script_dir_remote,
|
|
164
165
|
prefix=(
|
|
165
166
|
f"{int(time.time())}_"
|
|
166
|
-
f"{
|
|
167
|
+
f"{TaskGroupActivityAction.REACTIVATE}"
|
|
167
168
|
),
|
|
168
169
|
logger_name=LOGGER_NAME,
|
|
169
170
|
fractal_ssh=fractal_ssh,
|
|
@@ -233,9 +234,7 @@ def reactivate_ssh_pixi(
|
|
|
233
234
|
remote_script3_path,
|
|
234
235
|
f"chmod -R 755 {source_dir}",
|
|
235
236
|
],
|
|
236
|
-
slurm_config=resource.tasks_pixi_config[
|
|
237
|
-
"SLURM_CONFIG"
|
|
238
|
-
],
|
|
237
|
+
slurm_config=resource.tasks_pixi_config["SLURM_CONFIG"],
|
|
239
238
|
fractal_ssh=fractal_ssh,
|
|
240
239
|
logger_name=LOGGER_NAME,
|
|
241
240
|
prefix=common_args["prefix"],
|
|
@@ -248,7 +247,7 @@ def reactivate_ssh_pixi(
|
|
|
248
247
|
activity = add_commit_refresh(obj=activity, db=db)
|
|
249
248
|
|
|
250
249
|
# Finalize (write metadata to DB)
|
|
251
|
-
activity.status =
|
|
250
|
+
activity.status = TaskGroupActivityStatus.OK
|
|
252
251
|
activity.timestamp_ended = get_timestamp()
|
|
253
252
|
activity = add_commit_refresh(obj=activity, db=db)
|
|
254
253
|
task_group.active = True
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
1
3
|
set -e
|
|
2
4
|
|
|
3
5
|
# Variables to be filled within fractal-server
|
|
@@ -7,4 +9,4 @@ PACKAGE_ENV_DIR=__PACKAGE_ENV_DIR__
|
|
|
7
9
|
ENV_DISK_USAGE=$(du -sk "${PACKAGE_ENV_DIR}" | cut -f1)
|
|
8
10
|
ENV_FILE_NUMBER=$(find "${PACKAGE_ENV_DIR}" -type f | wc -l)
|
|
9
11
|
|
|
10
|
-
echo $ENV_DISK_USAGE $ENV_FILE_NUMBER
|
|
12
|
+
echo "$ENV_DISK_USAGE" "$ENV_FILE_NUMBER"
|
|
@@ -6,10 +6,10 @@ from sqlalchemy.orm import Session as DBSyncSession
|
|
|
6
6
|
|
|
7
7
|
from fractal_server.app.models.v2 import TaskGroupActivityV2
|
|
8
8
|
from fractal_server.app.models.v2 import TaskGroupV2
|
|
9
|
-
from fractal_server.app.schemas.v2 import
|
|
10
|
-
from fractal_server.app.schemas.v2 import
|
|
9
|
+
from fractal_server.app.schemas.v2 import TaskCreate
|
|
10
|
+
from fractal_server.app.schemas.v2 import TaskGroupActivityStatus
|
|
11
11
|
from fractal_server.app.schemas.v2.manifest import ManifestV2
|
|
12
|
-
from fractal_server.app.schemas.v2.task_group import
|
|
12
|
+
from fractal_server.app.schemas.v2.task_group import TaskGroupActivityAction
|
|
13
13
|
from fractal_server.exceptions import UnreachableBranchError
|
|
14
14
|
from fractal_server.logger import get_logger
|
|
15
15
|
from fractal_server.logger import reset_logger_handlers
|
|
@@ -66,11 +66,11 @@ def fail_and_cleanup(
|
|
|
66
66
|
f"Original error: {str(exception)}"
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
-
task_group_activity.status =
|
|
69
|
+
task_group_activity.status = TaskGroupActivityStatus.FAILED
|
|
70
70
|
task_group_activity.timestamp_ended = get_timestamp()
|
|
71
71
|
task_group_activity.log = get_current_log(log_file_path)
|
|
72
72
|
task_group_activity = add_commit_refresh(obj=task_group_activity, db=db)
|
|
73
|
-
if task_group_activity.action ==
|
|
73
|
+
if task_group_activity.action == TaskGroupActivityAction.COLLECT:
|
|
74
74
|
db.delete(task_group)
|
|
75
75
|
db.commit()
|
|
76
76
|
reset_logger_handlers(logger)
|
|
@@ -83,7 +83,7 @@ def prepare_tasks_metadata(
|
|
|
83
83
|
python_bin: Path | None = None,
|
|
84
84
|
project_python_wrapper: Path | None = None,
|
|
85
85
|
package_version: str | None = None,
|
|
86
|
-
) -> list[
|
|
86
|
+
) -> list[TaskCreate]:
|
|
87
87
|
"""
|
|
88
88
|
Based on the package manifest and additional info, prepare the task list.
|
|
89
89
|
|
|
@@ -112,9 +112,9 @@ def prepare_tasks_metadata(
|
|
|
112
112
|
if package_version is not None:
|
|
113
113
|
task_attributes["version"] = package_version
|
|
114
114
|
if package_manifest.has_args_schemas:
|
|
115
|
-
task_attributes[
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
task_attributes["args_schema_version"] = (
|
|
116
|
+
package_manifest.args_schema_version
|
|
117
|
+
)
|
|
118
118
|
# Set command attributes
|
|
119
119
|
if _task.executable_non_parallel is not None:
|
|
120
120
|
non_parallel_path = package_root / _task.executable_non_parallel
|
|
@@ -129,7 +129,7 @@ def prepare_tasks_metadata(
|
|
|
129
129
|
)
|
|
130
130
|
task_attributes["command_parallel"] = cmd_parallel
|
|
131
131
|
# Create object
|
|
132
|
-
task_obj =
|
|
132
|
+
task_obj = TaskCreate(
|
|
133
133
|
**_task.model_dump(
|
|
134
134
|
exclude={
|
|
135
135
|
"executable_non_parallel",
|
|
@@ -3,13 +3,13 @@ from sqlalchemy.orm import Session as DBSyncSession
|
|
|
3
3
|
|
|
4
4
|
from fractal_server.app.models.v2 import TaskGroupV2
|
|
5
5
|
from fractal_server.app.models.v2 import TaskV2
|
|
6
|
-
from fractal_server.app.schemas.v2 import
|
|
6
|
+
from fractal_server.app.schemas.v2 import TaskCreate
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def create_db_tasks_and_update_task_group_sync(
|
|
10
10
|
*,
|
|
11
11
|
task_group_id: int,
|
|
12
|
-
task_list: list[
|
|
12
|
+
task_list: list[TaskCreate],
|
|
13
13
|
db: DBSyncSession,
|
|
14
14
|
) -> TaskGroupV2:
|
|
15
15
|
"""
|
|
@@ -17,7 +17,7 @@ def create_db_tasks_and_update_task_group_sync(
|
|
|
17
17
|
|
|
18
18
|
Args:
|
|
19
19
|
task_group_id: ID of an existing `TaskGroupV2` object.
|
|
20
|
-
task_list: List of `
|
|
20
|
+
task_list: List of `TaskCreate` objects to be inserted into the db.
|
|
21
21
|
db: Synchronous database session
|
|
22
22
|
|
|
23
23
|
Returns:
|
|
@@ -36,7 +36,7 @@ def create_db_tasks_and_update_task_group_sync(
|
|
|
36
36
|
async def create_db_tasks_and_update_task_group_async(
|
|
37
37
|
*,
|
|
38
38
|
task_group_id: int,
|
|
39
|
-
task_list: list[
|
|
39
|
+
task_list: list[TaskCreate],
|
|
40
40
|
db: AsyncSession,
|
|
41
41
|
) -> TaskGroupV2:
|
|
42
42
|
"""
|
|
@@ -44,7 +44,7 @@ async def create_db_tasks_and_update_task_group_async(
|
|
|
44
44
|
|
|
45
45
|
Args:
|
|
46
46
|
task_group_id: ID of an existing `TaskGroupV2` object.
|
|
47
|
-
task_list: List of `
|
|
47
|
+
task_list: List of `TaskCreate` objects to be inserted into the db.
|
|
48
48
|
db: Synchronous database session
|
|
49
49
|
|
|
50
50
|
Returns:
|
|
@@ -62,8 +62,7 @@ def compare_package_names(
|
|
|
62
62
|
return
|
|
63
63
|
|
|
64
64
|
logger.warning(
|
|
65
|
-
f"Package name mismatch: "
|
|
66
|
-
f"{pkg_name_task_group=}, {pkg_name_pip_show=}."
|
|
65
|
+
f"Package name mismatch: {pkg_name_task_group=}, {pkg_name_pip_show=}."
|
|
67
66
|
)
|
|
68
67
|
normalized_pkg_name_pip = normalize_package_name(pkg_name_pip_show)
|
|
69
68
|
normalized_pkg_name_taskgroup = normalize_package_name(pkg_name_task_group)
|
|
@@ -95,9 +95,7 @@ def simplify_pyproject_toml(
|
|
|
95
95
|
if key == pixi_environment
|
|
96
96
|
}
|
|
97
97
|
if pixi_data["environments"] == {}:
|
|
98
|
-
raise ValueError(
|
|
99
|
-
f"No '{pixi_environment}' pixi environment found."
|
|
100
|
-
)
|
|
98
|
+
raise ValueError(f"No '{pixi_environment}' pixi environment found.")
|
|
101
99
|
except KeyError:
|
|
102
100
|
logger.info("KeyError for workspace/platforms - skip.")
|
|
103
101
|
|
fractal_server/types/__init__.py
CHANGED
|
@@ -6,9 +6,13 @@ from pydantic import AfterValidator
|
|
|
6
6
|
from pydantic.types import NonNegativeInt
|
|
7
7
|
from pydantic.types import StringConstraints
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from fractal_server.urls import normalize_url
|
|
10
|
+
|
|
10
11
|
from .validators import val_absolute_path
|
|
11
12
|
from .validators import val_http_url
|
|
13
|
+
from .validators import val_no_dotdot_in_path
|
|
14
|
+
from .validators import val_non_absolute_path
|
|
15
|
+
from .validators import val_os_path_normpath
|
|
12
16
|
from .validators import val_unique_list
|
|
13
17
|
from .validators import valdict_keys
|
|
14
18
|
from .validators import validate_attribute_filters
|
|
@@ -18,69 +22,162 @@ NonEmptyStr = Annotated[
|
|
|
18
22
|
str,
|
|
19
23
|
StringConstraints(min_length=1, strip_whitespace=True),
|
|
20
24
|
]
|
|
25
|
+
"""
|
|
26
|
+
A non-empty string, with no leading/trailing whitespaces.
|
|
27
|
+
"""
|
|
28
|
+
|
|
21
29
|
|
|
22
30
|
AbsolutePathStr = Annotated[
|
|
23
31
|
NonEmptyStr,
|
|
24
32
|
AfterValidator(val_absolute_path),
|
|
33
|
+
AfterValidator(val_no_dotdot_in_path),
|
|
34
|
+
AfterValidator(val_os_path_normpath),
|
|
25
35
|
]
|
|
36
|
+
"""
|
|
37
|
+
String representing an absolute path.
|
|
38
|
+
|
|
39
|
+
Validation fails if the path is not absolute or if it contains a
|
|
40
|
+
parent-directory reference "/../".
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
RelativePathStr = Annotated[
|
|
44
|
+
NonEmptyStr,
|
|
45
|
+
AfterValidator(val_no_dotdot_in_path),
|
|
46
|
+
AfterValidator(val_os_path_normpath),
|
|
47
|
+
AfterValidator(val_non_absolute_path),
|
|
48
|
+
]
|
|
49
|
+
|
|
26
50
|
HttpUrlStr = Annotated[
|
|
27
51
|
NonEmptyStr,
|
|
28
52
|
AfterValidator(val_http_url),
|
|
29
53
|
]
|
|
54
|
+
"""
|
|
55
|
+
String representing an URL.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
|
|
30
59
|
ZarrUrlStr = Annotated[
|
|
31
60
|
NonEmptyStr,
|
|
61
|
+
AfterValidator(val_no_dotdot_in_path),
|
|
32
62
|
AfterValidator(normalize_url),
|
|
33
63
|
]
|
|
64
|
+
"""
|
|
65
|
+
String representing a zarr URL/path.
|
|
66
|
+
|
|
67
|
+
Validation fails if the path is not absolute or if it contains a
|
|
68
|
+
parent-directory reference "/../".
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
34
72
|
ZarrDirStr = Annotated[
|
|
35
73
|
NonEmptyStr,
|
|
74
|
+
AfterValidator(val_no_dotdot_in_path),
|
|
36
75
|
AfterValidator(normalize_url),
|
|
37
76
|
]
|
|
77
|
+
"""
|
|
78
|
+
String representing a `zarr_dir` path.
|
|
79
|
+
|
|
80
|
+
Validation fails if the path is not absolute or if it contains a
|
|
81
|
+
parent-directory reference "/../".
|
|
82
|
+
"""
|
|
38
83
|
|
|
39
84
|
DictStrAny = Annotated[
|
|
40
85
|
dict[str, Any],
|
|
41
86
|
AfterValidator(valdict_keys),
|
|
42
87
|
]
|
|
88
|
+
"""
|
|
89
|
+
Dictionary where keys are strings with no leading/trailing whitespaces.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
|
|
43
93
|
DictStrStr = Annotated[
|
|
44
94
|
dict[str, NonEmptyStr],
|
|
45
95
|
AfterValidator(valdict_keys),
|
|
46
96
|
]
|
|
97
|
+
"""
|
|
98
|
+
Dictionary where keys are strings with no leading/trailing whitespaces and
|
|
99
|
+
values are non-empty strings.
|
|
100
|
+
"""
|
|
47
101
|
|
|
48
102
|
ListUniqueNonEmptyString = Annotated[
|
|
49
103
|
list[NonEmptyStr],
|
|
50
104
|
AfterValidator(val_unique_list),
|
|
51
105
|
]
|
|
106
|
+
"""
|
|
107
|
+
List of unique non-empty-string items.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
|
|
52
111
|
ListUniqueNonNegativeInt = Annotated[
|
|
53
112
|
list[NonNegativeInt],
|
|
54
113
|
AfterValidator(val_unique_list),
|
|
55
114
|
]
|
|
115
|
+
"""
|
|
116
|
+
List of unique non-negative-integer items.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
|
|
56
120
|
ListUniqueAbsolutePathStr = Annotated[
|
|
57
121
|
list[AbsolutePathStr],
|
|
58
122
|
AfterValidator(val_unique_list),
|
|
59
123
|
]
|
|
124
|
+
"""
|
|
125
|
+
List of unique absolute-path-string items.
|
|
126
|
+
"""
|
|
60
127
|
|
|
61
128
|
WorkflowTaskArgument = Annotated[
|
|
62
129
|
DictStrAny,
|
|
63
130
|
AfterValidator(validate_wft_args),
|
|
64
131
|
]
|
|
132
|
+
"""
|
|
133
|
+
Dictionary with no keys from a given forbid-list.
|
|
134
|
+
"""
|
|
65
135
|
|
|
66
136
|
ImageAttributeValue = Union[int, float, str, bool]
|
|
137
|
+
"""
|
|
138
|
+
Possible values for image attributes.
|
|
139
|
+
"""
|
|
140
|
+
|
|
67
141
|
ImageAttributes = Annotated[
|
|
68
142
|
dict[str, ImageAttributeValue],
|
|
69
143
|
AfterValidator(valdict_keys),
|
|
70
144
|
]
|
|
145
|
+
"""
|
|
146
|
+
Image-attributes dictionary.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
|
|
71
150
|
ImageAttributesWithNone = Annotated[
|
|
72
151
|
dict[str, ImageAttributeValue | None],
|
|
73
152
|
AfterValidator(valdict_keys),
|
|
74
153
|
]
|
|
154
|
+
"""
|
|
155
|
+
Image-attributes dictionary, including `None` attributes.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
|
|
75
159
|
AttributeFilters = Annotated[
|
|
76
160
|
dict[str, list[ImageAttributeValue]],
|
|
77
161
|
AfterValidator(validate_attribute_filters),
|
|
78
162
|
]
|
|
163
|
+
"""
|
|
164
|
+
Image-attributes filters.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
|
|
79
168
|
TypeFilters = Annotated[
|
|
80
169
|
dict[str, bool],
|
|
81
170
|
AfterValidator(valdict_keys),
|
|
82
171
|
]
|
|
172
|
+
"""
|
|
173
|
+
Image-type filters.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
|
|
83
177
|
ImageTypes = Annotated[
|
|
84
178
|
dict[str, bool],
|
|
85
179
|
AfterValidator(valdict_keys),
|
|
86
180
|
]
|
|
181
|
+
"""
|
|
182
|
+
Image types.
|
|
183
|
+
"""
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from ._common_validators import val_absolute_path # noqa F401
|
|
2
|
+
from ._common_validators import val_no_dotdot_in_path # noqa F401
|
|
3
|
+
from ._common_validators import val_os_path_normpath # noqa F401
|
|
2
4
|
from ._common_validators import val_http_url # noqa F401
|
|
3
5
|
from ._common_validators import val_unique_list # noqa F401
|
|
4
6
|
from ._common_validators import valdict_keys # noqa F401
|
|
7
|
+
from ._common_validators import val_non_absolute_path # noqa F401
|
|
5
8
|
from ._filter_validators import validate_attribute_filters # noqa F401
|
|
6
9
|
from ._workflow_task_arguments_validators import validate_wft_args # noqa F401
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
from typing import Any
|
|
3
4
|
|
|
4
5
|
from pydantic import HttpUrl
|
|
@@ -13,9 +14,7 @@ def valdict_keys(d: dict[str, Any]) -> dict[str, Any]:
|
|
|
13
14
|
if any(k == "" for k in new_keys):
|
|
14
15
|
raise ValueError(f"Empty string in {new_keys}.")
|
|
15
16
|
if len(new_keys) != len(set(new_keys)):
|
|
16
|
-
raise ValueError(
|
|
17
|
-
f"Dictionary contains multiple identical keys: '{d}'."
|
|
18
|
-
)
|
|
17
|
+
raise ValueError(f"Dictionary contains multiple identical keys: '{d}'.")
|
|
19
18
|
for old_key, new_key in zip(old_keys, new_keys):
|
|
20
19
|
if new_key != old_key:
|
|
21
20
|
d[new_key] = d.pop(old_key)
|
|
@@ -31,6 +30,37 @@ def val_absolute_path(path: str) -> str:
|
|
|
31
30
|
return path
|
|
32
31
|
|
|
33
32
|
|
|
33
|
+
def val_non_absolute_path(path: str) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Check that a string attribute is not an absolute path
|
|
36
|
+
"""
|
|
37
|
+
if os.path.isabs(path):
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"String must not be an absolute path (given '{path}')."
|
|
40
|
+
)
|
|
41
|
+
return path
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def val_no_dotdot_in_path(path: str) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Check that a string attribute has no '/../' in it
|
|
47
|
+
"""
|
|
48
|
+
if ".." in Path(path).parts:
|
|
49
|
+
raise ValueError("String must not contain '/../'.")
|
|
50
|
+
return path
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def val_os_path_normpath(path: str) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Apply `os.path.normpath` to `path`.
|
|
56
|
+
|
|
57
|
+
Note: we keep this separate from `fractal_server.urls.normalize_url`,
|
|
58
|
+
because this function only applies to on-disk paths, while `normalize_url`
|
|
59
|
+
may apply to s3 URLs as well.
|
|
60
|
+
"""
|
|
61
|
+
return os.path.normpath(path)
|
|
62
|
+
|
|
63
|
+
|
|
34
64
|
def val_unique_list(must_be_unique: list) -> list:
|
|
35
65
|
if len(set(must_be_unique)) != len(must_be_unique):
|
|
36
66
|
raise ValueError("List has repetitions")
|
|
@@ -4,7 +4,6 @@ def validate_wft_args(value: dict) -> dict:
|
|
|
4
4
|
intersect_keys = RESERVED_ARGUMENTS.intersection(args_keys)
|
|
5
5
|
if intersect_keys:
|
|
6
6
|
raise ValueError(
|
|
7
|
-
"`args` contains the following forbidden keys: "
|
|
8
|
-
f"{intersect_keys}"
|
|
7
|
+
f"`args` contains the following forbidden keys: {intersect_keys}"
|
|
9
8
|
)
|
|
10
9
|
return value
|
fractal_server/utils.py
CHANGED
fractal_server/zip_tools.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shutil
|
|
3
|
+
import subprocess # nosec
|
|
3
4
|
from collections.abc import Iterator
|
|
4
5
|
from io import BytesIO
|
|
5
6
|
from pathlib import Path
|
|
@@ -144,3 +145,36 @@ def _zip_folder_to_file_and_remove(folder: str) -> None:
|
|
|
144
145
|
logger.info(f"Removing folder '{folder}'.")
|
|
145
146
|
shutil.rmtree(folder)
|
|
146
147
|
logger.info("Folder removed.")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _read_single_file_from_zip(*, file_path: str, archive_path: str) -> str:
|
|
151
|
+
"""
|
|
152
|
+
Reads and returns the contents of a single file from a ZIP archive using
|
|
153
|
+
`unzip -p`.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
file_path:
|
|
157
|
+
relative to the archive
|
|
158
|
+
archive_path:
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The file content
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
FileNotFoundError:
|
|
165
|
+
if the file is not inside the archive
|
|
166
|
+
"""
|
|
167
|
+
result = subprocess.run( # nosec
|
|
168
|
+
["unzip", "-p", archive_path, file_path],
|
|
169
|
+
capture_output=True,
|
|
170
|
+
encoding="utf-8",
|
|
171
|
+
check=False,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if result.returncode != 0:
|
|
175
|
+
# The caller function should handle this error
|
|
176
|
+
raise FileNotFoundError(
|
|
177
|
+
f"File '{file_path}' not found inside archive '{archive_path}'."
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return result.stdout
|