From ba1a3b9f692bc5f47fa20beb3e74e4e9da8ee234 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sun, 5 Jul 2026 09:17:37 +0200 Subject: [PATCH] LazyTool: answer get_panel_section off the entry (no materialise) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AgentTools.get_tool_categories iterates toolbox.tools() and reads tool.get_panel_section()[1] on every visible tool. get_panel_section was not on the LazyTool stub surface, so under the permissive default that sweep materialised the entire toolbox to build the category list (and under LAZY_TOOL_STRICT=1 it would now raise). The populator already stamps panel_section_id / panel_section_name onto ToolIndexEntry, so forward the call off the entry — matching the (section_id, section_name) / (None, None) contract of Tool.get_panel_section — and keep the walk lazy. Claude-Session: https://claude.ai/code/session_018L7ZmCv2ubKA3JNeSL8Pkr --- lib/galaxy/tools/lazy_toolbox.py | 16 ++++++++++++++++ test/unit/app/tools/test_lazy_tool.py | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/galaxy/tools/lazy_toolbox.py b/lib/galaxy/tools/lazy_toolbox.py index cba7c109f2e..e0cf6387f70 100644 --- a/lib/galaxy/tools/lazy_toolbox.py +++ b/lib/galaxy/tools/lazy_toolbox.py @@ -252,6 +252,22 @@ class LazyTool: def lineage(self): return self._lineage + def get_panel_section(self) -> tuple[str, str] | tuple[None, None]: + """Answer the tool's ``(section_id, section_name)`` off the entry. + + ``Tool.get_panel_section`` resolves this through + ``toolbox.get_section_for_tool`` (a panel lookup), but the populator + already stamps the placement onto ``ToolIndexEntry``. Full-toolbox + sweeps read this — e.g. ``AgentTools.get_tool_categories`` iterates + ``toolbox.tools()`` and reads ``get_panel_section()[1]`` per tool — + so forwarding off the entry keeps that O(N) walk from materialising + every tool. + """ + entry = self._entry + if entry.panel_section_id: + return (entry.panel_section_id, entry.panel_section_name or "") + return (None, None) + @property def version_object(self): # Mirror ``Tool.version_object`` (lib/galaxy/tools/__init__.py:1213). diff --git a/test/unit/app/tools/test_lazy_tool.py b/test/unit/app/tools/test_lazy_tool.py index 538e7b4d805..2d117edf1e8 100644 --- a/test/unit/app/tools/test_lazy_tool.py +++ b/test/unit/app/tools/test_lazy_tool.py @@ -155,6 +155,16 @@ def test_to_panel_entry_carries_client_contract_fields(): assert "config_file" not in d +def test_get_panel_section_answered_off_entry_without_materialise(): + # AgentTools.get_tool_categories sweeps the whole toolbox and reads + # get_panel_section()[1] per tool; forwarding off the entry keeps that + # walk from parsing every tool. _stub's default materialize raises. + e = _entry(panel_section_id="ngs", panel_section_name="NGS: Mapping") + assert _stub(e).get_panel_section() == ("ngs", "NGS: Mapping") + # No section stamped -> (None, None), matching Tool.get_panel_section. + assert _stub(_entry()).get_panel_section() == (None, None) + + def test_to_dict_materialises(): calls: list[Any] = []