update_index_entry: write through to the per-version map

The data_manager_id stamp landed in index.entries only —
update_index_entry never touched entries_by_version, and the two maps
serialize independently. Job-time materialise passes a tool_version, so
it resolves through the per-version map and got the unstamped entry
back after the next index reload (populate broadcasts one constantly):
exec_after_process still failed with 'Invalid data manager (<guid>)'.

Both backends now route updates through ToolIndex.add_entry, which
keeps the maps in step (and stops an older-version update from
clobbering the newer default entry). create_tool additionally stamps
the in-memory per-version twin, which is a distinct object after a
from_dict reload.
This commit is contained in:
mvdbeek
2026-07-28 17:27:25 +02:00
parent 3aaecbe65d
commit 6f0f950ed5
4 changed files with 35 additions and 1 deletions
+5 -1
View File
@@ -310,7 +310,11 @@ class DatabaseToolSourceStore(ToolSourceStore):
if index is None:
index = ToolIndex()
index.entries[entry.id] = entry
# add_entry keeps ``entries_by_version`` in step with ``entries`` —
# versioned lookups (``get(tool_id, tool_version)``, the job-time
# materialise path) read the per-version map, and the two maps are
# serialized independently.
index.add_entry(entry)
index.invalidate_caches()
# Update section mapping
+7
View File
@@ -801,6 +801,13 @@ class LazyToolBox(ToolBox):
# id and misses the registry. Persist so job-handler processes
# materialising from the shared index see it too.
entry.data_manager_id = data_manager_id
if self._tool_index is not None:
# After a from_dict reload the default and per-version maps
# hold distinct objects; the job-time materialise resolves
# through the per-version map, so stamp its twin too.
twin = self._tool_index.entries_by_version.get(entry.id, {}).get(entry.version or "")
if twin is not None and twin is not entry:
twin.data_manager_id = data_manager_id
if self._store is not None:
try:
self._store.update_index_entry(entry)
+5
View File
@@ -360,9 +360,14 @@ def test_create_tool_stamps_data_manager_conf_id_on_entry():
box = _seam_box()
e = _entry(id="dm_tool", tool_type="manage_data")
box._tool_index.entries["dm_tool"] = e
# After a from_dict reload the per-version map holds a distinct object —
# the one job-time versioned lookups resolve. It must get the stamp too.
twin = _entry(id="dm_tool", tool_type="manage_data")
box._tool_index.entries_by_version["dm_tool"] = {twin.version or "": twin}
tool = box.create_tool(config_file=None, guid="dm_tool", data_manager_id="test_data_manager")
assert isinstance(tool, LazyTool)
assert e.data_manager_id == "test_data_manager"
assert twin.data_manager_id == "test_data_manager"
box._store.update_index_entry.assert_called_once_with(e)
@@ -213,6 +213,24 @@ class TestDatabaseBackendPathRows:
assert reloaded.get("removable") is None
assert "removable" not in reloaded.by_section.get("sec1", [])
def test_update_index_entry_reaches_versioned_lookups(self):
app = MockApp()
store = DatabaseToolSourceStore(app.model.context) # type: ignore[arg-type]
index = ToolIndex()
index.add_entry(ToolIndexEntry(id="dm_tool", version="1.0", name="DM"))
store.store_index(index)
app.model.context.commit()
store.update_index_entry(ToolIndexEntry(id="dm_tool", version="1.0", name="DM", data_manager_id="conf_id"))
app.model.context.commit()
store.invalidate_index_cache()
reloaded = store.load_index()
assert reloaded is not None
versioned = reloaded.get("dm_tool", "1.0")
assert versioned is not None
assert versioned.data_manager_id == "conf_id"
def test_pathless_sources_dedupe_on_hash(self):
app = MockApp()
store = DatabaseToolSourceStore(app.model.context) # type: ignore[arg-type]