Skip to content

Commit 031c027

Browse files
committed
LazyToolBox: stamp guid + tool-shed metadata on shed-install entries, refresh views
Two follow-on issues from the prior shed-install commit: - ``test_run_workflow_optional_data_provided_runs_step``: workflow invocation looked up the installed tool by full toolshed guid via ``has_tool(guid)``. ``_make_index_entry`` keys the entry on ``tool_source.parse_id()`` (the short id from the XML body, e.g. ``map_param_value``); the install path supplied the guid as ``fallback_tool_id`` but ``parse_id()`` won and the index ended up keyed on the short id. ``has_tool(guid)`` then missed and invocation 400'd with "required tools are not installed". - ``test_only_latest_version_in_panel_fastp``: install put fastp in ``test_section_multi`` and ``custom_13`` panel view should expose it. The view's ``_tool_panel_view_rendered`` snapshot was rendered at boot — ``StaticToolPanelView.apply_view`` calls ``closest_section.copy(merge_tools=True)`` which freezes the section's elems at render time, so a post-boot install was invisible to ``tools?in_panel=True&view=...``. Fix: - After ``_build_index_entry_from_stored`` returns, override ``entry.id`` with the install guid and stamp ``is_local=False`` plus the tool_shed/repository_name/owner/ changeset_revision pulled from the install elem. ``add_entry`` then indexes both ``entries[guid]`` and ``entries_by_version[guid]``. - Pass ``guid=stored.tool_id`` into ``create_tool_from_source`` when the stored ``tool_id`` is a toolshed guid so the lazy-loaded Tool's ``id`` matches. - After a section install, invalidate ``_integrated_tool_panel._materialised_sections`` for the affected section and re-run ``_load_tool_panel_views`` so static views re-render with the new tool.
1 parent 1c1ad02 commit 031c027

1 file changed

Lines changed: 66 additions & 8 deletions

File tree

lib/galaxy/tools/lazy_toolbox.py

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,9 +1230,26 @@ def _lazy_register_section_item(self, item, tool_path: str, index: Optional[int]
12301230
"""
12311231
section_id = item.get("id")
12321232
section_name = item.get("name", section_id) or ""
1233-
if section_id and section_id not in self._tool_panel:
1233+
if section_id:
12341234
section_dict = {"id": section_id, "name": section_name, "version": item.get("version", "")}
1235-
self._tool_panel.append_section(section_id, ToolSection(section_dict))
1235+
if section_id not in self._tool_panel:
1236+
self._tool_panel.append_section(section_id, ToolSection(section_dict))
1237+
# Mirror into ``_integrated_tool_panel`` so static panel views
1238+
# (``apply_view`` walks the integrated panel via
1239+
# ``ToolPanelElements.closest_section`` / ``walk_sections``)
1240+
# can find the section. Without this, e.g. ``custom_13.yml``
1241+
# which references ``test_section_multi`` renders the
1242+
# freshly-installed shed tool at the panel root because the
1243+
# view doesn't see the section it was placed under.
1244+
if section_id not in self._integrated_tool_panel:
1245+
self._integrated_tool_panel[section_id] = ToolSection(section_dict)
1246+
# Drop the section from the materialised-set so the next view
1247+
# render pulls in the newly-installed tool. The lazy panel
1248+
# otherwise short-circuits on the already-materialised flag and
1249+
# keeps serving the pre-install snapshot.
1250+
if isinstance(self._integrated_tool_panel, LazyIntegratedToolPanelElements):
1251+
self._integrated_tool_panel._materialised_sections.discard(section_id)
1252+
self._integrated_tool_panel._fully_materialised = False
12361253
if not hasattr(item, "items"):
12371254
return
12381255
for sub_item in item.items:
@@ -1249,6 +1266,20 @@ def _lazy_register_section_item(self, item, tool_path: str, index: Optional[int]
12491266
# labels, workflows, nested sections — let the eager
12501267
# implementation handle them; they don't need lazy treatment.
12511268
super().load_item(sub_item, tool_path=tool_path, index=index)
1269+
# Re-render static panel views so newly-installed tools land in
1270+
# ``_tool_panel_view_rendered`` immediately. The view dict is
1271+
# otherwise frozen at boot — ``apply_view`` calls
1272+
# ``closest_section.copy(merge_tools=True)`` which snapshots the
1273+
# section's elems at render time, so a post-boot install is
1274+
# invisible to ``tools?in_panel=True&view=...`` until the next
1275+
# reload. Skip if ``app.name != "galaxy"`` (mirrors the eager
1276+
# ``_load_tool_panel_views`` guard in
1277+
# ``ToolBox._load_built_in_converters``).
1278+
if getattr(self.app, "name", None) == "galaxy":
1279+
try:
1280+
self._load_tool_panel_views()
1281+
except Exception as e:
1282+
log.warning(f"Lazy section install: panel view re-render failed: {e}")
12521283

12531284
def _lazy_register_tool_item(
12541285
self,
@@ -1316,7 +1347,30 @@ def _lazy_register_tool_item(
13161347
return
13171348

13181349
entry = self._build_index_entry_from_stored(stored)
1319-
if entry and entry.id:
1350+
if entry:
1351+
# ``_build_index_entry_from_stored`` keys the entry on
1352+
# ``tool_source.parse_id()`` (the XML's ``<tool id="...">``,
1353+
# i.e. the short id like ``map_param_value``). For shed
1354+
# installs the tool's *external* id is the guid — that's what
1355+
# ``Tool.id`` becomes in the eager path and what
1356+
# workflow / API callers consult via ``has_tool(guid)``.
1357+
# Override the entry id (and any tool-shed metadata) so the
1358+
# lazy index agrees with that contract.
1359+
if installed_guid:
1360+
entry.id = installed_guid
1361+
entry.is_local = False
1362+
shed_url = item.findtext("tool_shed") if hasattr(item, "findtext") else None
1363+
if shed_url:
1364+
entry.tool_shed = shed_url
1365+
repo_name = item.findtext("repository_name") if hasattr(item, "findtext") else None
1366+
if repo_name:
1367+
entry.repository_name = repo_name
1368+
repo_owner = item.findtext("repository_owner") if hasattr(item, "findtext") else None
1369+
if repo_owner:
1370+
entry.repository_owner = repo_owner
1371+
changeset = item.findtext("installed_changeset_revision") if hasattr(item, "findtext") else None
1372+
if changeset:
1373+
entry.changeset_revision = changeset
13201374
if section_id:
13211375
entry.panel_section_id = section_id
13221376
entry.panel_section_name = section_name or section_id
@@ -1646,11 +1700,15 @@ def _create_tool_from_stored_source(self, stored: StoredToolSource) -> "Tool":
16461700
raw_tool_source=stored.raw_source,
16471701
tool_source_class=stored.tool_source_class,
16481702
)
1649-
return create_tool_from_source(
1650-
self.app,
1651-
tool_source,
1652-
tool_dir=stored.tool_dir,
1653-
)
1703+
# When the stored source's ``tool_id`` is a toolshed guid (set by
1704+
# the lazy shed install path), pass it as ``guid`` to the Tool
1705+
# constructor so ``Tool.id`` becomes the guid — matching what the
1706+
# eager toolbox does and what callers consult via ``has_tool``
1707+
# / ``get_tool`` and the ``_tools_by_id`` registry.
1708+
kwds: dict[str, Any] = {"tool_dir": stored.tool_dir}
1709+
if stored.tool_id and "/repos/" in stored.tool_id:
1710+
kwds["guid"] = stored.tool_id
1711+
return create_tool_from_source(self.app, tool_source, **kwds)
16541712

16551713
def invalidate_index_cache(self) -> None:
16561714
"""Drop cached tool index so the next read picks up out-of-band updates.

0 commit comments

Comments
 (0)