Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/modelseed_api/jobs/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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, ""],
Expand Down Expand Up @@ -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 = []
Expand Down
40 changes: 40 additions & 0 deletions tests/unit/test_ensure_ws_folder.py
Original file line number Diff line number Diff line change
@@ -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()