fix(searcher): guard against None metadata in CLI print path
`col.query(...)` can return `None` entries in the inner ``metadatas`` list
for drawers whose metadata was never set (older palaces, rows written
outside the normal mining path). The CLI `search()` function would render
earlier results successfully and then crash mid-loop with:
AttributeError: 'NoneType' object has no attribute 'get'
at ``searcher.py:286`` — ``meta.get("source_file", "?")``. The user sees
partial output followed by a traceback, with no indication of which
drawers rendered OK and which were skipped.
Guard with ``meta = meta or {}`` inside the loop so entries with missing
metadata fall back to the existing ``"?"`` defaults instead of crashing,
matching the hit dict assembly in ``search_memories()`` which already
uses ``meta.get("wing", "unknown")`` etc. against the same data.
Adds a regression test that mocks a ChromaDB result with a ``None``
metadata entry in the middle of the inner list and asserts both result
blocks render to stdout.
This commit is contained in:
@@ -283,6 +283,7 @@ def search(query: str, palace_path: str, wing: str = None, room: str = None, n_r
|
||||
|
||||
for i, (doc, meta, dist) in enumerate(zip(docs, metas, dists), 1):
|
||||
similarity = round(max(0.0, 1 - dist), 3)
|
||||
meta = meta or {}
|
||||
source = Path(meta.get("source_file", "?")).name
|
||||
wing_name = meta.get("wing", "?")
|
||||
room_name = meta.get("room", "?")
|
||||
|
||||
@@ -141,3 +141,22 @@ class TestSearchCLI:
|
||||
captured = capsys.readouterr()
|
||||
# Should have output with at least one result block
|
||||
assert "[1]" in captured.out
|
||||
|
||||
def test_search_handles_none_metadata_without_crash(self, palace_path, capsys):
|
||||
"""ChromaDB can return `None` entries in the metadatas list when a
|
||||
drawer has no metadata. The CLI print path must not crash on them
|
||||
mid-render — it used to raise `AttributeError: 'NoneType' object has
|
||||
no attribute 'get'` after printing earlier results."""
|
||||
mock_col = MagicMock()
|
||||
mock_col.query.return_value = {
|
||||
"documents": [["first doc", "second doc"]],
|
||||
"metadatas": [[{"source_file": "a.md", "wing": "w", "room": "r"}, None]],
|
||||
"distances": [[0.1, 0.2]],
|
||||
}
|
||||
with patch("mempalace.searcher.get_collection", return_value=mock_col):
|
||||
search("anything", "/fake/path")
|
||||
captured = capsys.readouterr()
|
||||
assert "[1]" in captured.out
|
||||
assert "[2]" in captured.out
|
||||
# Second result renders with fallback '?' values instead of crashing
|
||||
assert "second doc" in captured.out
|
||||
|
||||
Reference in New Issue
Block a user