Coverage for haystack/components/converters/file_to_file_content.py: 100%
32 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 13:53 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 13:53 +0000
1# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2#
3# SPDX-License-Identifier: Apache-2.0
5import base64
6from pathlib import Path
7from typing import Any
9from haystack import component, logging
10from haystack.components.converters.utils import get_bytestream_from_source, normalize_metadata
11from haystack.dataclasses import ByteStream, FileContent
13logger = logging.getLogger(__name__)
16_EMPTY_BYTE_STRING = b""
19@component
20class FileToFileContent:
21 """
22 Converts files to FileContent objects to be included in ChatMessage objects.
24 ### Usage example
25 <!-- test-ignore -->
26 ```python
27 from haystack.components.converters import FileToFileContent
29 converter = FileToFileContent()
30 sources = ["test/test_files/pdf/react_paper.pdf", "test/test_files/images/haystack-logo.png"]
31 file_contents = converter.run(sources=sources)["file_contents"]
33 print(file_contents)
34 # >> [FileContent(base64_data='...', mime_type='application/pdf', filename='react_paper.pdf', extra={}),
35 # >> FileContent(base64_data='...', mime_type='image/png', filename='haystack-logo.png', extra={})
36 # >>]
37 ```
38 """
40 @component.output_types(file_contents=list[FileContent])
41 def run(
42 self, sources: list[str | Path | ByteStream], *, extra: dict[str, Any] | list[dict[str, Any]] | None = None
43 ) -> dict[str, list[FileContent]]:
44 """
45 Converts files to FileContent objects.
47 :param sources:
48 List of file paths or ByteStream objects to convert.
49 :param extra:
50 Optional extra information to attach to the FileContent objects. Can be used to store provider-specific
51 information.
52 To avoid serialization issues, values should be JSON serializable.
53 This value can be a list of dictionaries or a single dictionary.
54 If it's a single dictionary, its content is added to the extra of all produced FileContent objects.
55 If it's a list, its length must match the number of sources as they're zipped together.
57 :returns:
58 A dictionary with the following keys:
59 - `file_contents`: A list of FileContent objects.
60 """
61 if not sources:
62 return {"file_contents": []}
64 file_contents = []
66 extra_list = normalize_metadata(extra, sources_count=len(sources))
68 for source, extra_dict in zip(sources, extra_list, strict=True):
69 if isinstance(source, str):
70 source = Path(source)
72 filename = source.name if isinstance(source, Path) else None
74 try:
75 bytestream = get_bytestream_from_source(source, guess_mime_type=True)
76 except Exception as e:
77 logger.warning("Could not read {source}. Skipping it. Error: {error}", source=source, error=e)
78 continue
80 if bytestream.data == _EMPTY_BYTE_STRING:
81 logger.warning("File {source} is empty. Skipping it.", source=source)
82 continue
84 base64_data = base64.b64encode(bytestream.data).decode("utf-8")
85 file_content = FileContent(
86 base64_data=base64_data, mime_type=bytestream.mime_type, filename=filename, extra=extra_dict
87 )
88 file_contents.append(file_content)
90 return {"file_contents": file_contents}