Skip to content

Commit a3c7782

Browse files
committed
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.
1 parent 74a31b7 commit a3c7782

2 files changed

Lines changed: 20 additions & 0 deletions

File tree

mempalace/searcher.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ def search(query: str, palace_path: str, wing: str = None, room: str = None, n_r
283283

284284
for i, (doc, meta, dist) in enumerate(zip(docs, metas, dists), 1):
285285
similarity = round(max(0.0, 1 - dist), 3)
286+
meta = meta or {}
286287
source = Path(meta.get("source_file", "?")).name
287288
wing_name = meta.get("wing", "?")
288289
room_name = meta.get("room", "?")

tests/test_searcher.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,22 @@ def test_search_n_results(self, palace_path, seeded_collection, capsys):
141141
captured = capsys.readouterr()
142142
# Should have output with at least one result block
143143
assert "[1]" in captured.out
144+
145+
def test_search_handles_none_metadata_without_crash(self, palace_path, capsys):
146+
"""ChromaDB can return `None` entries in the metadatas list when a
147+
drawer has no metadata. The CLI print path must not crash on them
148+
mid-render — it used to raise `AttributeError: 'NoneType' object has
149+
no attribute 'get'` after printing earlier results."""
150+
mock_col = MagicMock()
151+
mock_col.query.return_value = {
152+
"documents": [["first doc", "second doc"]],
153+
"metadatas": [[{"source_file": "a.md", "wing": "w", "room": "r"}, None]],
154+
"distances": [[0.1, 0.2]],
155+
}
156+
with patch("mempalace.searcher.get_collection", return_value=mock_col):
157+
search("anything", "/fake/path")
158+
captured = capsys.readouterr()
159+
assert "[1]" in captured.out
160+
assert "[2]" in captured.out
161+
# Second result renders with fallback '?' values instead of crashing
162+
assert "second doc" in captured.out

0 commit comments

Comments
 (0)