From efc27186d4afd931a6b1d91772cadecc8d4169b1 Mon Sep 17 00:00:00 2001 From: jplfaria Date: Tue, 16 Jun 2026 20:43:41 +0000 Subject: [PATCH 1/2] fix(reconstruct): ensure workspace folder exists before first write --- src/modelseed_api/jobs/tasks.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/modelseed_api/jobs/tasks.py b/src/modelseed_api/jobs/tasks.py index 771191d..0e2edb1 100644 --- a/src/modelseed_api/jobs/tasks.py +++ b/src/modelseed_api/jobs/tasks.py @@ -72,6 +72,18 @@ def _merge_ws_metadata(ws, obj_path: str, new_meta: dict): ws.update_metadata({"objects": [[obj_path, merged]]}) +def _ensure_ws_folder(ws, path: str) -> None: + """Create workspace folder at path if it does not exist (idempotent). + + PATRIC workspace returns Insufficient permissions instead of Not Found + when creating inside a missing intermediate directory. This is the root + cause for first-time users who have no /username/modelseed folder yet. + """ + if not path or path.rstrip("/") in ("", "/"): + return + ws.create({"objects": [[path.rstrip("/"), "folder", {}, ""]], "overwrite": 1}) + + def _load_template(template_type: str): """Load a template from local JSON file using MSTemplateBuilder.""" from modelseedpy import MSTemplateBuilder @@ -768,6 +780,7 @@ def reconstruct( "fba_count": "0", } + _ensure_ws_folder(ws, output_path.rsplit("/", 1)[0]) ws.create({ "objects": [ [output_path, "modelfolder", folder_meta, ""], @@ -1342,6 +1355,8 @@ def _progress(msg: str) -> None: data_dir = str(kbann.annoontology_dir / "data") ws = get_storage_service(token) + _ensure_ws_folder(ws, output_path.rsplit("/", 1)[0]) + _ensure_ws_folder(ws, output_path) _progress(f"Preparing batch of {len(genomes)} genome(s)...") all_rxn_rows: list = [] From 6c4edf4458ab1cdde7c26ab5910cdb45e5cdbefa Mon Sep 17 00:00:00 2001 From: jplfaria Date: Tue, 16 Jun 2026 20:44:48 +0000 Subject: [PATCH 2/2] test(tasks): add unit tests for _ensure_ws_folder helper --- tests/unit/test_ensure_ws_folder.py | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/unit/test_ensure_ws_folder.py diff --git a/tests/unit/test_ensure_ws_folder.py b/tests/unit/test_ensure_ws_folder.py new file mode 100644 index 0000000..f4fe1ca --- /dev/null +++ b/tests/unit/test_ensure_ws_folder.py @@ -0,0 +1,40 @@ +"""Tests for _ensure_ws_folder. + +Regression for ModelSEED/modelseed-api-ops#11: PATRIC workspace returns +"Insufficient permissions" instead of "Not Found" when creating inside +a missing intermediate directory, which hits first-time users who have +not yet had a /username/modelseed folder created. +""" +from unittest.mock import MagicMock + +from modelseed_api.jobs.tasks import _ensure_ws_folder + + +def test_creates_folder(): + ws = MagicMock() + _ensure_ws_folder(ws, "/alice/modelseed") + ws.create.assert_called_once_with( + {"objects": [["/alice/modelseed", "folder", {}, ""]], "overwrite": 1} + ) + + +def test_strips_trailing_slash(): + ws = MagicMock() + _ensure_ws_folder(ws, "/alice/modelseed/") + ws.create.assert_called_once_with( + {"objects": [["/alice/modelseed", "folder", {}, ""]], "overwrite": 1} + ) + + +def test_noop_for_root_and_empty(): + ws = MagicMock() + _ensure_ws_folder(ws, "") + _ensure_ws_folder(ws, "/") + _ensure_ws_folder(ws, "//") + ws.create.assert_not_called() + + +def test_noop_for_none(): + ws = MagicMock() + _ensure_ws_folder(ws, None) + ws.create.assert_not_called()