lazy toolbox: restore converter and membership filters in tests_summary

This commit is contained in:
mvdbeek
2026-07-28 17:27:47 +02:00
parent 1610c6d96f
commit bf421f1c59
5 changed files with 86 additions and 3 deletions
+9 -2
View File
@@ -80,6 +80,12 @@ class ToolIndexEntry(BaseModel):
# ``interactive_tool``, ``data_source``, ...). Filter authors and
# ``DataManagerTool.allow_user_access`` (admin-only) both branch on this.
tool_type: str = "default"
# True for datatype-converter discoveries (``CONVERTER_TOOL_CONF``). The
# ``tool_type`` alone can't identify them — converters keep whatever type
# their XML declares — so the populator stamps this flag from the
# discovery source. ``tests_summary`` excludes them to mirror the eager
# ``if not tool.is_datatype_converter`` filter.
is_datatype_converter: bool = False
# User-facing tags from ``<tool>`` config (distinct from ``labels``).
# Surfaced for custom tool filters that bucket tools by tag.
tags: list[str] = Field(default_factory=list)
@@ -396,8 +402,9 @@ class ToolIndex(BaseModel):
summary: dict[str, dict[str, dict]] = {}
for entry in self.entries.values():
# Match the eager fallback in services.tools.ToolsService.get_tests_summary:
# tools without tests are excluded entirely.
if not entry.test_count:
# tools without tests, and datatype converters, are excluded entirely
# (the eager loop skips ``tool.is_datatype_converter``).
if not entry.test_count or entry.is_datatype_converter:
continue
if entry.id not in summary:
summary[entry.id] = {}
@@ -498,6 +498,7 @@ def build_index_entry_from_source(
version=version,
name=tool_source.parse_name() or "",
description=tool_source.parse_description() or "",
is_datatype_converter=discovered.tool_conf == CONVERTER_TOOL_CONF,
icon=icon,
xrefs=xrefs,
is_workflow_compatible=is_workflow_compatible,
+7 -1
View File
@@ -575,7 +575,13 @@ class ToolsService(ServiceBase):
"""
lazy_toolbox = self._get_lazy_toolbox(trans)
if lazy_toolbox and lazy_toolbox.tool_index:
return lazy_toolbox.tool_index.get_tests_summary()
# The index is store-wide and ``get_tests_summary`` already drops
# datatype converters; scope the result to tools this toolbox
# actually holds so ids present in the store but never loaded here
# don't leak into the summary (the eager loop below only ever sees
# loaded tools).
summary = lazy_toolbox.tool_index.get_tests_summary()
return {tool_id: versions for tool_id, versions in summary.items() if lazy_toolbox.has_tool(tool_id)}
# Fallback to traditional toolbox iteration
test_counts_by_tool: dict[str, dict] = {}
@@ -110,3 +110,11 @@ def test_add_entry_invalidates_derived_metadata(index_entry, tool_index):
assert [requirement["name"] for requirement in index.get_all_requirements()] == ["one", "two"]
assert set(index.get_tests_summary()) == {"one", "two"}
def test_tests_summary_excludes_datatype_converters(index_entry, tool_index):
index = tool_index(
index_entry("real_tool", version="1.0", test_count=2),
index_entry("convert_fasta", version="1.0", test_count=1, is_datatype_converter=True),
)
assert set(index.get_tests_summary()) == {"real_tool"}
@@ -0,0 +1,61 @@
"""Pin ``build_index_entry_from_source`` metadata capture."""
from datetime import (
datetime,
timezone,
)
from galaxy.tool_util.parser import get_tool_source
from galaxy.tools.source_store.discover import (
CONVERTER_TOOL_CONF,
DiscoveredTool,
)
from galaxy.tools.source_store.interface import StoredToolSource
from galaxy.tools.source_store.populator import (
build_index_entry_from_source,
MAX_HELP_TEXT_CHARS,
)
_TOOL_XML = """<tool id="help_tool" name="Help Tool" version="1.0">
<command>echo</command>
<inputs/>
<outputs/>
<help>This wraps the quaxifier subroutine.</help>
</tool>
"""
_TOOL_XML_NO_HELP = """<tool id="plain_tool" name="Plain Tool" version="1.0">
<command>echo</command>
<inputs/>
<outputs/>
</tool>
"""
def _stored(path, tool_source):
return StoredToolSource(
hash="deadbeef",
tool_source_class=type(tool_source).__name__,
raw_source=tool_source.to_string(),
tool_id=tool_source.parse_id(),
tool_version=tool_source.parse_version(),
tool_dir=str(path.parent),
source_path=str(path),
stored_at=datetime.now(timezone.utc),
metadata={},
)
def _build(tmp_path, xml, tool_conf="tool_conf.xml"):
path = tmp_path / "tool.xml"
path.write_text(xml)
tool_source = get_tool_source(config_file=str(path))
discovered = DiscoveredTool(path=str(path), tool_conf=tool_conf, tool_path=str(tmp_path))
return build_index_entry_from_source(discovered, _stored(path, tool_source), tool_source)
def test_entry_marks_datatype_converter(tmp_path):
plain = _build(tmp_path, _TOOL_XML)
converter = _build(tmp_path, _TOOL_XML, tool_conf=CONVERTER_TOOL_CONF)
assert plain is not None and plain.is_datatype_converter is False
assert converter is not None and converter.is_datatype_converter is True