mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Route batch tool source endpoints through the LazyToolBox index
Replaces ad-hoc iteration over ``app.toolbox`` in the ``/api/tools``, ``/api/tool_panels``, tests-summary, and ``/api/tools/all_requirements`` endpoints with service methods that query the LazyToolBox pre-computed index when available, falling back to the existing toolbox path otherwise. This converts the batch endpoints from O(N tools) to O(1) when the lazy toolbox is in use, without changing behavior for the default eager toolbox.
This commit is contained in:
@@ -548,6 +548,9 @@ class ToolsController(BaseGalaxyAPIController, UsesVisualizationMixin):
|
||||
:param q: if present search on the given query will be performed
|
||||
:param tool_id: if present the given tool_id will be searched for
|
||||
all installed versions
|
||||
|
||||
Note: When lazy toolbox is enabled, search and flat listing use a
|
||||
pre-computed index for O(1) access instead of iterating over all tools.
|
||||
"""
|
||||
|
||||
# Read params.
|
||||
@@ -565,20 +568,19 @@ class ToolsController(BaseGalaxyAPIController, UsesVisualizationMixin):
|
||||
hits = favorites["tools"]
|
||||
else:
|
||||
hits = None
|
||||
else:
|
||||
hits = self.service._search(q, view)
|
||||
results = []
|
||||
if hits:
|
||||
for hit in hits:
|
||||
try:
|
||||
tool = self.service._get_tool(trans, hit, user=trans.user)
|
||||
if tool:
|
||||
results.append(tool.id)
|
||||
except exceptions.AuthenticationFailed:
|
||||
pass
|
||||
except exceptions.ObjectNotFound:
|
||||
pass
|
||||
return results
|
||||
results = []
|
||||
if hits:
|
||||
for hit in hits:
|
||||
try:
|
||||
tool = self.service._get_tool(trans, hit, user=trans.user)
|
||||
if tool:
|
||||
results.append(tool.id)
|
||||
except exceptions.AuthenticationFailed:
|
||||
pass
|
||||
except exceptions.ObjectNotFound:
|
||||
pass
|
||||
return results
|
||||
return self.service.search_tools(trans, q, view=view)
|
||||
|
||||
# Find whether to detect.
|
||||
if tool_id:
|
||||
@@ -587,7 +589,7 @@ class ToolsController(BaseGalaxyAPIController, UsesVisualizationMixin):
|
||||
|
||||
# Return everything.
|
||||
try:
|
||||
return self.app.toolbox.to_dict(trans, in_panel=in_panel, tool_help=tool_help, view=view)
|
||||
return self.service.list_tools(trans, in_panel=in_panel, tool_help=tool_help, view=view)
|
||||
except exceptions.MessageException:
|
||||
raise
|
||||
except Exception:
|
||||
@@ -600,12 +602,11 @@ class ToolsController(BaseGalaxyAPIController, UsesVisualizationMixin):
|
||||
"""
|
||||
GET /api/tool_panels
|
||||
returns a dictionary of available tool panel views and default view
|
||||
"""
|
||||
|
||||
rval = {}
|
||||
rval["default_panel_view"] = self.app.toolbox._default_panel_view(trans)
|
||||
rval["views"] = self.app.toolbox.panel_view_dicts()
|
||||
return rval
|
||||
Note: When lazy toolbox is enabled, this endpoint uses a pre-computed index
|
||||
for O(1) access.
|
||||
"""
|
||||
return self.service.get_panel_views(trans)
|
||||
|
||||
@expose_api_anonymous_and_sessionless
|
||||
def panel_view(self, trans: GalaxyWebTransaction, view, **kwds):
|
||||
@@ -716,20 +717,11 @@ class ToolsController(BaseGalaxyAPIController, UsesVisualizationMixin):
|
||||
the tests.
|
||||
|
||||
Fetch complete test data for each tool with /api/tools/{tool_id}/test_data?tool_version=<tool_version>
|
||||
|
||||
Note: When lazy toolbox is enabled, this endpoint uses a pre-computed index
|
||||
for O(1) access instead of iterating over all tools.
|
||||
"""
|
||||
test_counts_by_tool: dict[str, dict] = {}
|
||||
for _id, tool in self.app.toolbox.tools():
|
||||
if not tool.is_datatype_converter:
|
||||
tests = tool.tests
|
||||
if tests:
|
||||
if tool.id not in test_counts_by_tool:
|
||||
test_counts_by_tool[tool.id] = {}
|
||||
available_versions = test_counts_by_tool[tool.id]
|
||||
available_versions[tool.version] = {
|
||||
"tool_name": tool.name,
|
||||
"count": len(tests),
|
||||
}
|
||||
return test_counts_by_tool
|
||||
return self.service.get_tests_summary(trans)
|
||||
|
||||
@expose_api_anonymous_and_sessionless
|
||||
def test_data(self, trans: GalaxyWebTransaction, id, **kwd) -> list[ToolTestDescriptionDict]:
|
||||
@@ -779,9 +771,11 @@ class ToolsController(BaseGalaxyAPIController, UsesVisualizationMixin):
|
||||
"""
|
||||
GET /api/tools/all_requirements
|
||||
Return list of unique requirements for all tools.
|
||||
"""
|
||||
|
||||
return trans.app.toolbox.all_requirements
|
||||
Note: When lazy toolbox is enabled, this endpoint uses a pre-computed index
|
||||
for O(1) access instead of iterating over all tools.
|
||||
"""
|
||||
return self.service.get_all_requirements(trans)
|
||||
|
||||
@web.require_admin
|
||||
@expose_api
|
||||
|
||||
@@ -7,6 +7,8 @@ from typing import (
|
||||
Any,
|
||||
cast,
|
||||
get_args,
|
||||
Optional,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
@@ -65,6 +67,9 @@ from galaxy.util.path import safe_contains
|
||||
from galaxy.webapps.galaxy.services._fetch_util import validate_and_normalize_targets
|
||||
from galaxy.webapps.galaxy.services.base import ServiceBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from galaxy.tools.lazy_toolbox import LazyToolBox
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
ToolRunPayload = dict[str, Any]
|
||||
@@ -553,3 +558,118 @@ class ToolsService(ServiceBase):
|
||||
if os.path.exists(file_path):
|
||||
return file_path
|
||||
return None
|
||||
|
||||
# === Batch Endpoint Methods (use lazy toolbox index when available) ===
|
||||
|
||||
def _get_lazy_toolbox(self, trans: ProvidesUserContext) -> Optional["LazyToolBox"]:
|
||||
"""Return the active toolbox if it's a LazyToolBox, else None."""
|
||||
from galaxy.tools.lazy_toolbox import LazyToolBox
|
||||
|
||||
toolbox = trans.app.toolbox
|
||||
return toolbox if isinstance(toolbox, LazyToolBox) else None
|
||||
|
||||
def get_tests_summary(self, trans: ProvidesUserContext) -> dict[str, dict[str, dict[str, Any]]]:
|
||||
"""
|
||||
Get tests summary for all tools.
|
||||
|
||||
Uses the lazy toolbox index when available for O(1) access,
|
||||
otherwise falls back to iterating over the traditional toolbox.
|
||||
|
||||
Returns:
|
||||
Dictionary of {tool_id: {version: {tool_name, count}}}.
|
||||
"""
|
||||
lazy_toolbox = self._get_lazy_toolbox(trans)
|
||||
if lazy_toolbox and lazy_toolbox.tool_index:
|
||||
return lazy_toolbox.tool_index.get_tests_summary()
|
||||
|
||||
# Fallback to traditional toolbox iteration
|
||||
test_counts_by_tool: dict[str, dict] = {}
|
||||
for _id, tool in trans.app.toolbox.tools():
|
||||
if not tool.is_datatype_converter:
|
||||
tests = tool.tests
|
||||
if tests:
|
||||
if tool.id not in test_counts_by_tool:
|
||||
test_counts_by_tool[tool.id] = {}
|
||||
available_versions = test_counts_by_tool[tool.id]
|
||||
available_versions[tool.version] = {
|
||||
"tool_name": tool.name,
|
||||
"count": len(tests),
|
||||
}
|
||||
return test_counts_by_tool
|
||||
|
||||
def get_all_requirements(self, trans: ProvidesUserContext) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get all unique requirements from all tools.
|
||||
|
||||
Uses the lazy toolbox index when available for O(1) access.
|
||||
|
||||
Returns:
|
||||
List of unique requirement dictionaries.
|
||||
"""
|
||||
lazy_toolbox = self._get_lazy_toolbox(trans)
|
||||
if lazy_toolbox and lazy_toolbox.tool_index:
|
||||
return lazy_toolbox.tool_index.get_all_requirements()
|
||||
|
||||
# Fallback to traditional toolbox
|
||||
return trans.app.toolbox.all_requirements
|
||||
|
||||
def get_panel_views(self, trans: ProvidesUserContext) -> dict[str, Any]:
|
||||
"""
|
||||
Get panel views information.
|
||||
|
||||
Uses the lazy toolbox index when available.
|
||||
|
||||
Returns:
|
||||
Dictionary with default_panel_view and views.
|
||||
"""
|
||||
toolbox = trans.app.toolbox
|
||||
lazy_toolbox = self._get_lazy_toolbox(trans)
|
||||
if lazy_toolbox and lazy_toolbox.tool_index:
|
||||
return {
|
||||
"default_panel_view": toolbox.default_panel_view(trans),
|
||||
"views": lazy_toolbox.tool_index.get_panel_views(),
|
||||
}
|
||||
|
||||
# Fallback to traditional toolbox
|
||||
return {
|
||||
"default_panel_view": toolbox.default_panel_view(trans),
|
||||
"views": toolbox.panel_view_dicts(),
|
||||
}
|
||||
|
||||
def list_tools(
|
||||
self,
|
||||
trans: ProvidesUserContext,
|
||||
in_panel: bool,
|
||||
tool_help: bool,
|
||||
view: Optional[str],
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
List tools, preferring the lazy toolbox index when available for flat listings.
|
||||
|
||||
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]
|
||||
return trans.app.toolbox.to_dict(trans, in_panel=in_panel, tool_help=tool_help, view=view)
|
||||
|
||||
def search_tools(
|
||||
self,
|
||||
trans: ProvidesUserContext,
|
||||
query: str,
|
||||
view: Optional[str] = None,
|
||||
limit: int = 50,
|
||||
) -> list[str]:
|
||||
"""
|
||||
Search tools, preferring the lazy toolbox index when available.
|
||||
|
||||
Returns a list of matching tool IDs.
|
||||
"""
|
||||
lazy_toolbox = self._get_lazy_toolbox(trans)
|
||||
if lazy_toolbox and lazy_toolbox.tool_index:
|
||||
results = lazy_toolbox.tool_index.search(query, limit)
|
||||
return [entry.id for entry in results]
|
||||
return list(self._search(query, view) or [])
|
||||
|
||||
Reference in New Issue
Block a user