Skip to content

Commit e69572a

Browse files
authored
fix: CSVToDocument row mode no longer crashes the batch on a row with extra fields (#11944)
1 parent c1cd1d7 commit e69572a

3 files changed

Lines changed: 36 additions & 1 deletion

File tree

haystack/components/converters/csv.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,12 @@ def run(
163163

164164
# Create DictReader; if this fails, raise (no fallback)
165165
try:
166-
reader = csv.DictReader(io.StringIO(data), delimiter=self.delimiter, quotechar=self.quotechar)
166+
# ``restkey`` ensures surplus fields on ragged rows (rows with more values than the
167+
# header, e.g. an unquoted comma inside a value) land under an explicit string key
168+
# instead of the default ``None`` key, which would break ``Document`` id generation.
169+
reader = csv.DictReader(
170+
io.StringIO(data), delimiter=self.delimiter, quotechar=self.quotechar, restkey="extra_columns"
171+
)
167172
except Exception as e:
168173
raise RuntimeError(f"CSVToDocument(row): could not parse CSV rows for {source}: {e}") from e
169174

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed ``CSVToDocument`` in ``conversion_mode="row"`` crashing the entire batch when a CSV row
5+
has more fields than the header (ragged rows, e.g. an unquoted comma inside a value). Surplus
6+
values are now stored under an explicit ``extra_columns`` metadata key instead of the ``None``
7+
key, which previously broke Document id generation and discarded results from all sources.

test/components/converters/test_csv_to_document.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,26 @@ def broken_reader(*_args, **_kwargs): # noqa: D401
219219
monkeypatch.setattr(csv_mod.csv, "DictReader", broken_reader, raising=True)
220220
with pytest.raises(RuntimeError):
221221
_ = conv.run(sources=[f], content_column="a")
222+
223+
def test_row_mode_ragged_row_does_not_crash(self):
224+
# A data row with more fields than the header (e.g. an unquoted comma inside a value).
225+
# Previously the surplus value landed under the None key, which broke Document id
226+
# generation (TypeError sorting None against str keys) and aborted the whole batch.
227+
valid = ByteStream(data=b"text,author\r\nfine,Ada\r\n", meta={"file_path": "valid.csv"})
228+
ragged = ByteStream(data=b"text,note\r\nhello,city,state\r\n", meta={"file_path": "ragged.csv"})
229+
230+
conv = CSVToDocument(conversion_mode="row")
231+
out = conv.run(sources=[valid, ragged], content_column="text")
232+
docs = out["documents"]
233+
234+
# Both sources yielded a Document; the earlier valid source is not lost.
235+
assert len(docs) == 2
236+
assert docs[0].content == "fine"
237+
assert docs[0].meta["author"] == "Ada"
238+
239+
ragged_doc = docs[1]
240+
assert ragged_doc.content == "hello"
241+
assert ragged_doc.meta["note"] == "city"
242+
# Surplus value is preserved under an explicit (non-None) string meta key.
243+
assert None not in ragged_doc.meta
244+
assert "state" in ragged_doc.meta["extra_columns"]

0 commit comments

Comments
 (0)