Skip to content

Commit d956189

Browse files
committed
LazyToolBox: stamp + restore tool_shed metadata for shed installs
Verified locally with ``./run_tests.sh -integration ... test_only_latest_version_in_panel_fastp`` under ``GALAXY_CONFIG_OVERRIDE_USE_LAZY_TOOLBOX=true``: passes. Two fixes for the lazy shed-install path: 1. The install path's tool_shed metadata extraction (``tool_shed``/``repository_name``/``repository_owner``/ ``installed_changeset_revision``) used ``hasattr(item, "findtext")``, but ``item`` is a ``ToolConfItem`` wrapper (no ``findtext`` — the underlying XML is reachable via ``item.elem`` gated by ``item.has_elem``). The hasattr check always failed, so none of the metadata ever landed on the index entry. 2. ``_create_tool_from_stored_source`` now hands the matching installed ``ToolShedRepository`` to the Tool ctor when the stored id is a guid. ``Tool.populate_tool_shed_info`` then sets ``self.tool_shed`` / ``repository_name`` / ``changeset_revision`` so ``Tool.to_dict`` populates the ``tool_shed_repository`` key in the API response. Without this, ``test_only_latest_version_in_panel_fastp`` raised ``KeyError: 'tool_shed_repository'`` even after ``tools[0]`` pointed at the correct fastp guid.
1 parent 06f01f9 commit d956189

1 file changed

Lines changed: 70 additions & 14 deletions

File tree

lib/galaxy/tools/lazy_toolbox.py

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,18 +1400,28 @@ def _lazy_register_tool_item(
14001400
if installed_guid:
14011401
entry.id = installed_guid
14021402
entry.is_local = False
1403-
shed_url = item.findtext("tool_shed") if hasattr(item, "findtext") else None
1404-
if shed_url:
1405-
entry.tool_shed = shed_url
1406-
repo_name = item.findtext("repository_name") if hasattr(item, "findtext") else None
1407-
if repo_name:
1408-
entry.repository_name = repo_name
1409-
repo_owner = item.findtext("repository_owner") if hasattr(item, "findtext") else None
1410-
if repo_owner:
1411-
entry.repository_owner = repo_owner
1412-
changeset = item.findtext("installed_changeset_revision") if hasattr(item, "findtext") else None
1413-
if changeset:
1414-
entry.changeset_revision = changeset
1403+
# Pull tool_shed metadata from the install elem so the
1404+
# entry — and the lazy-loaded Tool — agrees with the
1405+
# eager toolbox's ``populate_tool_shed_info`` contract.
1406+
# The install path generates ``<tool guid="..."><tool_shed>...</tool_shed>...</tool>``
1407+
# via ``ToolPanelManager.generate_tool_elem``; ``ToolConfItem``
1408+
# exposes the underlying XML via ``.elem`` (and ``has_elem``
1409+
# tells us when that's safe). For dict-typed items there's
1410+
# no XML — skip silently.
1411+
xml_elem = item.elem if getattr(item, "has_elem", False) else None
1412+
if xml_elem is not None:
1413+
shed_url = xml_elem.findtext("tool_shed")
1414+
if shed_url:
1415+
entry.tool_shed = shed_url
1416+
repo_name = xml_elem.findtext("repository_name")
1417+
if repo_name:
1418+
entry.repository_name = repo_name
1419+
repo_owner = xml_elem.findtext("repository_owner")
1420+
if repo_owner:
1421+
entry.repository_owner = repo_owner
1422+
changeset = xml_elem.findtext("installed_changeset_revision")
1423+
if changeset:
1424+
entry.changeset_revision = changeset
14151425
if section_id:
14161426
entry.panel_section_id = section_id
14171427
entry.panel_section_name = section_name or section_id
@@ -1720,7 +1730,7 @@ def _load_tool_on_demand(self, tool_id: str, tool_version: Optional[str] = None)
17201730

17211731
# Create Tool object
17221732
try:
1723-
tool = self._create_tool_from_stored_source(stored)
1733+
tool = self._create_tool_from_stored_source(stored, entry=entry)
17241734
log.debug(f"Lazy-loaded tool: {tool_id}")
17251735
except Exception as e:
17261736
log.error(f"Error creating tool {tool_id}: {e}")
@@ -1735,7 +1745,9 @@ def _load_tool_on_demand(self, tool_id: str, tool_version: Optional[str] = None)
17351745

17361746
return tool
17371747

1738-
def _create_tool_from_stored_source(self, stored: StoredToolSource) -> "Tool":
1748+
def _create_tool_from_stored_source(
1749+
self, stored: StoredToolSource, entry: Optional[ToolIndexEntry] = None
1750+
) -> "Tool":
17391751
"""Create a Tool object from stored source."""
17401752
tool_source = get_tool_source(
17411753
raw_tool_source=stored.raw_source,
@@ -1749,8 +1761,52 @@ def _create_tool_from_stored_source(self, stored: StoredToolSource) -> "Tool":
17491761
kwds: dict[str, Any] = {"tool_dir": stored.tool_dir}
17501762
if stored.tool_id and "/repos/" in stored.tool_id:
17511763
kwds["guid"] = stored.tool_id
1764+
# Hand the matching ToolShedRepository to the Tool ctor so
1765+
# ``populate_tool_shed_info`` can stamp ``tool_shed`` /
1766+
# ``repository_name`` / ``changeset_revision`` /
1767+
# ``installed_changeset_revision`` onto the Tool. Without
1768+
# them, ``Tool.to_dict`` skips the ``tool_shed_repository``
1769+
# block (gated on ``self.tool_shed`` being truthy) and tests
1770+
# like ``test_only_latest_version_in_panel_fastp`` raise
1771+
# ``KeyError: 'tool_shed_repository'`` on the rendered
1772+
# response.
1773+
shed_repo = self._lookup_tool_shed_repository(stored, entry)
1774+
if shed_repo is not None:
1775+
kwds["tool_shed_repository"] = shed_repo
17521776
return create_tool_from_source(self.app, tool_source, **kwds)
17531777

1778+
def _lookup_tool_shed_repository(self, stored: StoredToolSource, entry: Optional[ToolIndexEntry]) -> Optional[Any]:
1779+
"""Resolve the installed ToolShedRepository for a stored shed tool.
1780+
1781+
Mirrors ``AbstractToolBox.get_tool_repository_from_xml_item`` but
1782+
sources the tool_shed / repository_name / repository_owner /
1783+
installed_changeset_revision from the index entry (stamped by
1784+
``_lazy_register_tool_item`` from the install elem) instead of
1785+
re-parsing the conf XML.
1786+
"""
1787+
if entry is None or entry.is_local:
1788+
return None
1789+
tool_shed = entry.tool_shed
1790+
repo_name = entry.repository_name
1791+
repo_owner = entry.repository_owner
1792+
installed_changeset = entry.changeset_revision
1793+
if not (tool_shed and repo_name and repo_owner and installed_changeset):
1794+
return None
1795+
try:
1796+
from galaxy.tool_shed.util.repository_util import get_installed_repository
1797+
1798+
return get_installed_repository(
1799+
self.app,
1800+
tool_shed=tool_shed,
1801+
name=repo_name,
1802+
owner=repo_owner,
1803+
installed_changeset_revision=installed_changeset,
1804+
from_cache=True,
1805+
)
1806+
except Exception as e:
1807+
log.debug(f"Lazy lookup of tool_shed_repository for {stored.tool_id} failed: {e}")
1808+
return None
1809+
17541810
def invalidate_index_cache(self) -> None:
17551811
"""Drop cached tool index so the next read picks up out-of-band updates.
17561812

0 commit comments

Comments
 (0)