Repository: deepset-ai/haystack
Pull request: refactor: make sure that Document's `id_hash_keys` has a valid value
### Related Issues

- fixes #6111

### Proposed Changes:

Now, if `id_hash_keys` is None or empty, the Document dataclass populates this field in `__post_init__`
using the default factory.

Having valid values of `id_hash_keys` avoids generating the same Id over and over again (see the original issue).

### How did you test it?
CI, new unit test.

### Checklist

- I have read the [contributors guidelines](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md) and the [code of conduct](https://github.com/deepset-ai/haystack/blob/main/code_of_conduct.txt)
- I have updated the related issue with new insights and changes
- I added unit tests and updated the docstrings
- I've used one of the [conventional commit types](https://www.conventionalcommits.org/en/v1.0.0/) for my PR title: `fix:`, `feat:`, `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`.
- I documented my code
- I ran [pre-commit hooks](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md#installation) and fixed any issue


Problem statement:
Some file converters do not handle `id_hash_keys` properly
**Describe the bug**
Some file converters allow an optional parameter `id_hash_keys`, which defaults to None and is then converted into an empty list (if not explicitly specified).

So, during Document creation, text is not considered to compute the Id and all Documents have the same Id.

**Expected behavior**
By default, the converters should produce different Ids based on the content.

**To Reproduce**
```python
from haystack.preview.components.file_converters import TextFileToDocument

with open('myfile.txt','w') as fo:
    fo.write('My name is Jonas')

with open('myotherfile.txt','w') as fo:
    fo.write('My name is Gregory')

converter = TextFileToDocument()
res=converter.run(paths=['myfile.txt','myotherfile.txt'])

print([doc.id for doc in res["documents"]])
# ['d6bd8c0aeee80fedb2dbf05c973100c3db04be90c072a88572533f2684131a36', 'd6bd8c0aeee80fedb2dbf05c973100c3db04be90c072a88572533f2684131a36']
# SAME ID!
```

**Affected components**
- `HTMLToDocument`
- `PyPDFToDocument`
- `TextFileToDocument`

**System:**
 - Haystack version (commit or version number): preview

Hints from issue discussion:

Commit message: reno

Patch to review:
diff --git a/haystack/preview/components/file_converters/html.py b/haystack/preview/components/file_converters/html.py
index cf947c6d22..bb120e52f8 100644
--- a/haystack/preview/components/file_converters/html.py
+++ b/haystack/preview/components/file_converters/html.py
@@ -25,7 +25,7 @@ def __init__(self, id_hash_keys: Optional[List[str]] = None):
         :param id_hash_keys: List of strings referencing the Document's attributes to generate its ID. Default: `None`
         """
         boilerpy3_import.check()
-        self.id_hash_keys = id_hash_keys or []
+        self.id_hash_keys = id_hash_keys
 
     def to_dict(self) -> Dict[str, Any]:
         """Serialize the component to a dictionary."""
@@ -58,7 +58,10 @@ def run(self, sources: List[Union[str, Path, ByteStream]]):
                 logger.warning("Failed to extract text from %s. Skipping it. Error: %s", source, conversion_e)
                 continue
 
-            document = Document(text=text, id_hash_keys=self.id_hash_keys)
+            if self.id_hash_keys:
+                document = Document(text=text, id_hash_keys=self.id_hash_keys)
+            else:
+                document = Document(text=text)
             documents.append(document)
 
         return {"documents": documents}
diff --git a/haystack/preview/components/file_converters/pypdf.py b/haystack/preview/components/file_converters/pypdf.py
index 6330bde5d8..aa93aa0404 100644
--- a/haystack/preview/components/file_converters/pypdf.py
+++ b/haystack/preview/components/file_converters/pypdf.py
@@ -28,7 +28,7 @@ def __init__(self, id_hash_keys: Optional[List[str]] = None):
             attributes. Default: `None`
         """
         pypdf_import.check()
-        self.id_hash_keys = id_hash_keys or []
+        self.id_hash_keys = id_hash_keys
 
     def to_dict(self) -> Dict[str, Any]:
         """
@@ -64,7 +64,10 @@ def run(self, sources: List[Union[str, Path, ByteStream]], id_hash_keys: Optiona
                 logger.warning("Could not read %s. Skipping it. Error message: %s", source, e)
                 continue
 
-            document = Document(text=text, id_hash_keys=id_hash_keys)
+            if id_hash_keys:
+                document = Document(text=text, id_hash_keys=id_hash_keys)
+            else:
+                document = Document(text=text)
             documents.append(document)
 
         return {"documents": documents}
diff --git a/haystack/preview/components/file_converters/txt.py b/haystack/preview/components/file_converters/txt.py
index 6f23d1c0d8..cb77cb857a 100644
--- a/haystack/preview/components/file_converters/txt.py
+++ b/haystack/preview/components/file_converters/txt.py
@@ -58,7 +58,7 @@ def __init__(
         self.remove_numeric_tables = remove_numeric_tables
         self.numeric_row_threshold = numeric_row_threshold
         self.valid_languages = valid_languages or []
-        self.id_hash_keys = id_hash_keys or []
+        self.id_hash_keys = id_hash_keys
         self.progress_bar = progress_bar
 
     def to_dict(self) -> Dict[str, Any]:
@@ -154,7 +154,10 @@ def run(
                     valid_languages,
                 )
 
-            document = Document(text=text, metadata=meta, id_hash_keys=id_hash_keys)
+            if id_hash_keys:
+                document = Document(text=text, metadata=meta, id_hash_keys=id_hash_keys)
+            else:
+                document = Document(text=text, metadata=meta)
             documents.append(document)
 
         return {"documents": documents}
diff --git a/releasenotes/notes/fix-converter-ids-b820a16d6a9da08b.yaml b/releasenotes/notes/fix-converter-ids-b820a16d6a9da08b.yaml
new file mode 100644
index 0000000000..e4f663745c
--- /dev/null
+++ b/releasenotes/notes/fix-converter-ids-b820a16d6a9da08b.yaml
@@ -0,0 +1,6 @@
+---
+preview:
+  - |
+    Ensure that File converters handle the parameter `id_hash_keys` correctly.
+    If this parameter is not specified, the ids are generated based on the
+    the content (text).


Code review comments:
diff_hunk: @@ -58,7 +58,10 @@ def run(self, sources: List[Union[str, Path, ByteStream]]):
                 logger.warning("Failed to extract text from %s. Skipping it. Error: %s", source, conversion_e)
                 continue
 
-            document = Document(text=text, id_hash_keys=self.id_hash_keys)
+            if self.id_hash_keys:
+                document = Document(text=text, id_hash_keys=self.id_hash_keys)
+            else:
+                document = Document(text=text)
path: haystack/preview/components/file_converters/html.py
text: @author:
The condition I introduced is not nice.

But if you pass `id_hash_keys=None`,
the correct default value (`["text", "array", "dataframe", "blob"]`) is not used.

I'm open to better solutions...