discover: capture parent section + conf labels per tool

``DiscoveredTool`` now carries ``section_id`` / ``section_name`` and the
conf-level ``labels="a,b"`` list parsed off the ``<tool>`` element. The
populator (next commit) stamps these onto ``ToolIndexEntry`` rows so
``LazyToolBox`` can drop the post-walk syncs (``_stamp_panel_sections_onto_index``
and ``_sync_tool_mutations_to_index``) — section + labels arrive in the
index by construction.

``_iter_tool_items`` now yields ``(item, parent_section)`` pairs so the
caller can read the enclosing section's ``id`` and ``name`` for each
discovered tool. ``tool_dir`` walks inherit the section of their
declaring ``<section>`` block.

Tests cover top-level, single-section, mixed, ``tool_dir`` propagation,
hidden preservation, labels parsing, and the dataclass default-factory
guard for the new ``labels: list`` field.
This commit is contained in:
mvdbeek
2026-07-28 17:27:16 +02:00
parent e914b2c3ad
commit d7a026712e
+32 -8
View File
@@ -15,7 +15,10 @@ from collections.abc import (
Iterable,
Iterator,
)
from dataclasses import dataclass
from dataclasses import (
dataclass,
field,
)
from pathlib import Path
from typing import (
Optional,
@@ -49,6 +52,15 @@ class DiscoveredTool:
# set; the lazy path needs the same flag to make ``hidden_tool_versions``
# in /api/tools/{id} match the eager toolbox.
hidden: bool = False
# Conf-level ``labels="a,b"`` on the ``<tool>`` element. Same population
# ownership as ``hidden`` above: parsed once at populator time so the
# toolbox doesn't have to re-discover them in a post-walk sync.
labels: list[str] = field(default_factory=list)
# Parent ``<section id="..." name="...">`` of this tool, if any. The
# populator stamps these onto ``ToolIndexEntry`` so the panel render in
# ``LazyToolBox`` doesn't need a separate post-walk pass to find them.
section_id: Optional[str] = None
section_name: Optional[str] = None
def get_tool_configs(config: "GalaxyAppConfiguration") -> list[str]:
@@ -132,19 +144,24 @@ def _resolve_file_template_kwds(root_dir: Optional[str]) -> dict[str, str]:
return {"model_tools_path": MODEL_TOOLS_PATH}
def _iter_tool_items(items: Iterable[ToolConfItem]) -> Iterator[ToolConfItem]:
def _iter_tool_items(
items: Iterable[ToolConfItem],
parent_section: Optional[ToolConfSection] = None,
) -> Iterator[tuple[ToolConfItem, Optional[ToolConfSection]]]:
"""
Recursively iterate over tool items, including those nested in sections.
Yields ``tool`` and ``tool_dir`` items. ``tool_dir`` items reference an
on-disk directory rather than a single file; the caller is responsible for
walking the directory.
Yields ``(item, parent_section)`` pairs for each ``tool`` and ``tool_dir``
entry. ``parent_section`` is the immediate enclosing ``ToolConfSection`` or
``None`` for top-level items. ``tool_dir`` items reference an on-disk
directory rather than a single file; the caller is responsible for walking
the directory.
"""
for item in items:
if item.type in ("tool", "tool_dir"):
yield item
yield item, parent_section
elif isinstance(item, ToolConfSection):
yield from _iter_tool_items(item.items)
yield from _iter_tool_items(item.items, parent_section=item)
def _looks_like_a_tool(path: str) -> bool:
@@ -234,7 +251,9 @@ def discover_tools_from_config(
# dropped at the os.path.exists check below.
file_template_kwds = _resolve_file_template_kwds(root_dir)
for item in _iter_tool_items(tool_conf_source.parse_items()):
for item, section in _iter_tool_items(tool_conf_source.parse_items()):
section_id = section.get("id") if section is not None else None
section_name = section.get("name") if section is not None else None
if item.type == "tool_dir":
dir_attr = item.get("dir")
if not dir_attr:
@@ -254,6 +273,8 @@ def discover_tools_from_config(
tool_path=resolved_tool_path,
guid=None,
is_shed_tool=is_shed_conf,
section_id=section_id,
section_name=section_name,
)
continue
@@ -283,6 +304,9 @@ def discover_tools_from_config(
guid=item.get("guid"),
is_shed_tool=is_shed_conf,
hidden=str(item.get("hidden", "false")).lower() == "true",
labels=list(item.labels or ()),
section_id=section_id,
section_name=section_name,
)