Skip to content

Commit 28668d7

Browse files
Vedant-Agarwalclaudebogdankostic
authored
fix: give each FileContent its own extra dict in FileToFileContent (#11872)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: bogdankostic <bogdankostic@web.de>
1 parent 67d1f71 commit 28668d7

3 files changed

Lines changed: 25 additions & 1 deletion

File tree

haystack/components/converters/file_to_file_content.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,12 @@ def run(
8282
continue
8383

8484
base64_data = base64.b64encode(bytestream.data).decode("utf-8")
85+
# ``normalize_metadata`` returns the same dict object for every source when ``extra`` is a
86+
# single dict (or ``None``), so give each FileContent its own copy. Otherwise mutating one
87+
# file's ``extra`` downstream would leak into all the others. The other converters avoid this
88+
# implicitly by merging ``extra`` into a fresh ``{**bytestream.meta, ...}`` dict.
8589
file_content = FileContent(
86-
base64_data=base64_data, mime_type=bytestream.mime_type, filename=filename, extra=extra_dict
90+
base64_data=base64_data, mime_type=bytestream.mime_type, filename=filename, extra=dict(extra_dict)
8791
)
8892
file_contents.append(file_content)
8993

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed ``FileToFileContent`` sharing a single ``extra`` dictionary across all produced ``FileContent``
5+
objects when ``extra`` is passed as one dict (or left as ``None``). Each ``FileContent`` now gets its
6+
own copy, so mutating one file's ``extra`` no longer leaks into the others.

test/components/converters/test_file_to_file_content.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,20 @@ def test_run_with_extra_list(self) -> None:
113113
assert results["file_contents"][0].extra == {"key": "value1"}
114114
assert results["file_contents"][1].extra == {"key": "value2"}
115115

116+
def test_run_with_extra_dict_does_not_share_reference(self) -> None:
117+
# A single ``extra`` dict is applied to every source; each FileContent must get its own copy
118+
# so that mutating one file's ``extra`` downstream does not leak into the others.
119+
converter = FileToFileContent()
120+
sources = ["./test/test_files/txt/doc_1.txt", "./test/test_files/txt/doc_2.txt"]
121+
results = converter.run(sources=sources, extra={"tenant": "acme"})
122+
123+
file_contents = results["file_contents"]
124+
assert len(file_contents) == 2
125+
assert file_contents[0].extra is not file_contents[1].extra
126+
127+
file_contents[0].extra["page"] = 1
128+
assert "page" not in file_contents[1].extra
129+
116130
def test_run_skips_empty_files_among_valid(self, caplog) -> None:
117131
byte_stream_empty = ByteStream(data=b"")
118132
valid_source = "./test/test_files/txt/doc_1.txt"

0 commit comments

Comments
 (0)