Skip to content

Commit 8e3897a

Browse files
committed
LazyLineageMap: always derive versions from the index
LazyLineageMap.get short-circuited on the parent's ``super().get()`` fallback, which builds the lineage from a single ``Tool`` object's version via ``ToolLineage.from_tool`` and memoises it. With a tool that has multiple indexed versions (e.g. ``__BUILD_LIST__`` 1.1.0 + 1.2.0), only the just-lazily-loaded version landed in ``tool.lineage.tool_versions``. That broke ``get_safe_version`` at workflow upload: ``WORKFLOW_SAFE_TOOL_VERSION_UPDATES["__BUILD_LIST__"]`` allows 1.0.0 → 1.1.0 but not 1.0.0 → 1.2.0; the safe-version walk over the truncated ``[1.2.0]`` lineage couldn't find a match, so ``ToolModule.__init__`` set ``self.tool = None``. The workflow ended up bound to the latest tool with state shaped for the requested old version, which then raised "Workflow step has upgrade messages" on invoke. Reproduced by API ``test_run_with_int_parameter_nested``. Fix: when the index has versions for the tool_id, always extend the lineage from the index — don't let the parent's single-Tool fallback freeze the version set.
1 parent 3ac9a4a commit 8e3897a

1 file changed

Lines changed: 32 additions & 24 deletions

File tree

lib/galaxy/tool_util/toolbox/lineages/factory.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -88,30 +88,38 @@ def __init__(self, app, versions_for: Optional[Callable[[str], Iterable[str]]] =
8888
self._versions_for = versions_for
8989

9090
def get(self, tool_id: str) -> Optional[ToolLineage]:
91-
# Fast path: already memoised.
92-
existing = super().get(tool_id)
93-
if existing is not None:
94-
return existing
95-
if self._versions_for is None:
96-
return None
97-
try:
98-
versions = list(self._versions_for(tool_id))
99-
except Exception:
100-
return None
101-
if not versions:
102-
return None
103-
lineage = ToolLineage(tool_id)
104-
for version in versions:
105-
if version:
106-
lineage.register_version(version)
107-
# Memoise under both the version-bearing id and the versionless id —
108-
# mirrors what ``LineageMap.register`` does, so subsequent lookups
109-
# for either form hit the cache.
110-
self.lineage_map[tool_id] = lineage
111-
versionless = remove_version_from_guid(tool_id)
112-
if versionless and versionless not in self.lineage_map:
113-
self.lineage_map[versionless] = lineage
114-
return lineage
91+
# Always source versions from the index when available — the parent's
92+
# fallback path builds a lineage from a single ``Tool`` object's
93+
# version (via ``ToolLineage.from_tool``) and memoises it, which
94+
# would freeze ``tool_versions`` at ``[just-loaded version]`` and
95+
# hide every other version present in the index. That breaks
96+
# ``get_safe_version`` (used by ``ToolModule.__init__`` at workflow
97+
# upload to map a pinned-but-missing tool_version onto the nearest
98+
# safe-upgrade version, e.g. ``__BUILD_LIST__`` 1.0.0 → 1.1.0): it
99+
# walks ``tool.lineage.tool_versions`` and only finds candidates
100+
# within ``WORKFLOW_SAFE_TOOL_VERSION_UPDATES``' bounds, so a
101+
# one-element ``[1.2.0]`` lineage misses 1.1.0 and the workflow
102+
# ends up bound to 1.2.0 with state shaped for 1.0.0.
103+
if self._versions_for is not None:
104+
try:
105+
versions = list(self._versions_for(tool_id))
106+
except Exception:
107+
versions = []
108+
if versions:
109+
lineage = self.lineage_map.get(tool_id)
110+
if lineage is None:
111+
lineage = ToolLineage(tool_id)
112+
self.lineage_map[tool_id] = lineage
113+
versionless = remove_version_from_guid(tool_id)
114+
if versionless and versionless not in self.lineage_map:
115+
self.lineage_map[versionless] = lineage
116+
for version in versions:
117+
if version:
118+
lineage.register_version(version)
119+
return lineage
120+
# Index has no entry for this tool_id — fall back to whatever the
121+
# parent class can derive (a registered Tool, etc.).
122+
return super().get(tool_id)
115123

116124

117125
__all__ = ("LazyLineageMap", "LineageMap")

0 commit comments

Comments
 (0)