tools service: restore FilterFactory + per-hit auth parity for lazy listings and search

This commit is contained in:
mvdbeek
2026-07-28 17:27:21 +02:00
parent eba90fff5a
commit 34b9f0d328
2 changed files with 28 additions and 40 deletions
+5 -34
View File
@@ -41,10 +41,7 @@ from galaxy.tool_util.toolbox.base import ToolConfRepository
from galaxy.util.tool_version import remove_version_from_guid
from galaxy.tool_util.parser import get_tool_source
from galaxy.tool_util.toolbox.lineages.interface import ToolLineage
from galaxy.tool_util.toolbox.panel import (
panel_item_types,
ToolSection,
)
from galaxy.tool_util.toolbox.panel import ToolSection
from . import (
create_tool_from_source,
ToolBox,
@@ -1240,33 +1237,7 @@ class LazyToolBox(ToolBox):
]
return []
# === Override to_dict for API responses ===
def to_dict(
self, trans, in_panel: bool = True, tool_help: bool = False, view: Optional[str] = None, **kwds
) -> list[dict[str, Any]]:
"""Toolbox API serialisation.
For ``in_panel=False`` (the flat ``/api/tools`` listing the
Galaxy client uses), walk ``_tools_by_id`` directly: every value
is a ``LazyTool`` stub or a real ``Tool``, both implement
``ToolFilterContext`` (``allow_user_access``, ``require_login``,
``tool_type``, ...) and ``to_dict(link_details=False)`` returns
the listing-shaped dict from the entry without parsing XML.
For ``in_panel=True`` (the section-aware response), defer to the
parent — it walks the eager-populated ``_tool_panel_view_rendered``
which already holds ``LazyTool`` stubs from the seam-driven boot.
"""
if in_panel:
return super().to_dict(trans, in_panel=True, tool_help=tool_help, view=view, **kwds)
filter_method = self._build_filter_method(trans)
rval = []
for _tool_id, tool in list(self._tools_by_id.items()):
if not filter_method(tool, panel_item_types.TOOL):
continue
rval.append(tool.to_dict(trans, link_details=False))
log.debug("LazyToolBox.to_dict: returning %d tools (in_panel=False)", len(rval))
return rval
# ``to_dict`` is NOT overridden: ``AbstractToolBox.to_dict`` runs the
# ``FilterFactory`` pass for both the panel and the flat listing, and
# its ``get_tool_to_dict`` serves ``LazyTool`` stubs via
# ``to_panel_entry`` — filtered AND non-materialising.
+23 -6
View File
@@ -656,11 +656,12 @@ class ToolsService(ServiceBase):
For panel listings or when the index is unavailable, falls back to the
traditional toolbox.
"""
if not in_panel:
lazy_toolbox = self._get_lazy_toolbox(trans)
if lazy_toolbox and lazy_toolbox.tool_index:
entries = lazy_toolbox.tool_index.list_all()
return [entry.to_api_dict() for entry in entries]
# Both modes go through ``AbstractToolBox.to_dict``: the flat listing
# runs the ``FilterFactory`` pass (admin/user tool filters and
# ``allow_user_access``) over every tool, and ``get_tool_to_dict``
# serves ``LazyTool`` stubs from the index without materialising —
# so lazy mode stays O(1) parses while honoring the same filters as
# the eager toolbox.
return trans.app.toolbox.to_dict(trans, in_panel=in_panel, tool_help=tool_help, view=view)
def search_tools(
@@ -677,5 +678,21 @@ class ToolsService(ServiceBase):
:class:`ToolBoxSearch` walking ``tool_cache``. Both expose the same
``search(q, panel_view, config)`` interface, so this method doesn't
branch on which toolbox flavour is active.
Every hit is resolved through :meth:`_get_tool` so per-tool access
control (``allow_user_access``, e.g. ``require_login`` tools for
anonymous users) filters the results — same contract as the old
controller loop. In lazy mode ``get_tool`` returns the registered
``LazyTool`` stub, so this does not materialise the hits.
"""
return list(self._search(query, view) or [])
results: list[str] = []
for hit in self._search(query, view) or []:
try:
tool = self._get_tool(trans, hit, user=trans.user)
if tool:
results.append(tool.id)
except exceptions.AuthenticationFailed:
pass
except exceptions.ObjectNotFound:
pass
return results