Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions haystack/components/caching/cache_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,17 @@ async def run_async(self, items: list[Any]) -> dict[str, Any]:
else:
misses.append(item)
return {"hits": found_documents, "misses": misses}

def close(self) -> None:
"""
Release the synchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close"):
self.document_store.close()

async def close_async(self) -> None:
"""
Release the asynchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close_async"):
await self.document_store.close_async()
14 changes: 14 additions & 0 deletions haystack/components/retrievers/auto_merging_retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,17 @@ async def _try_merge_level(docs_to_merge: list[Document], docs_to_return: list[D
return await _try_merge_level(merged_docs, docs_to_return)

return {"documents": await _try_merge_level(documents, [])}

def close(self) -> None:
"""
Release the synchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close"):
self.document_store.close()

async def close_async(self) -> None:
"""
Release the asynchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close_async"):
await self.document_store.close_async()
14 changes: 14 additions & 0 deletions haystack/components/retrievers/filter_retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,17 @@ async def run_async(self, filters: dict[str, Any] | None = None) -> dict[str, An
# 'ignore' since filter_documents_async is not defined in the Protocol but exists in the implementations
out_documents = await self.document_store.filter_documents_async(filters=filters or self.filters) # type: ignore[attr-defined]
return {"documents": out_documents}

def close(self) -> None:
"""
Release the synchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close"):
self.document_store.close()

async def close_async(self) -> None:
"""
Release the asynchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close_async"):
await self.document_store.close_async()
14 changes: 14 additions & 0 deletions haystack/components/retrievers/sentence_window_retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,17 @@ def _build_filter_conditions(self, split_id: int, window_size: int, source_ids:
*source_id_filters,
]
return {"operator": "AND", "conditions": conditions}

def close(self) -> None:
"""
Release the synchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close"):
self.document_store.close()

async def close_async(self) -> None:
"""
Release the asynchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close_async"):
await self.document_store.close_async()
14 changes: 14 additions & 0 deletions haystack/components/writers/document_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,17 @@ async def run_async(self, documents: list[Document], policy: DuplicatePolicy | N

documents_written = await self.document_store.write_documents_async(documents=documents, policy=policy)
return {"documents_written": documents_written}

def close(self) -> None:
"""
Release the synchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close"):
self.document_store.close()

async def close_async(self) -> None:
"""
Release the asynchronous resources of the underlying Document Store.
"""
if hasattr(self.document_store, "close_async"):
await self.document_store.close_async()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
features:
- |
Haystack components that use a document store now provide ``close`` and ``close_async`` methods for releasing
resources. These methods are available on: ``AutoMergingRetriever``, ``CacheChecker``, ``DocumentWriter``,
``FilterRetriever``, and ``SentenceWindowRetriever``. If the underlying Document Store does not implement the
corresponding method, calling ``close`` or ``close_async`` has no effect.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: Apache-2.0

from unittest.mock import MagicMock
from unittest.mock import MagicMock, Mock

import pytest

Expand Down Expand Up @@ -92,3 +92,14 @@ def test_filters_syntax(self):
checker.run(items=["https://example.com/1"])
valid_filters_syntax = {"field": "url", "operator": "==", "value": "https://example.com/1"}
mocked_docstore_class.filter_documents.assert_any_call(filters=valid_filters_syntax)

def test_close(self):
closable_document_store = Mock(spec=["close"])
checker = CacheChecker(document_store=closable_document_store, cache_field="url")
checker.close()
closable_document_store.close.assert_called_once_with()

nonclosable_document_store = Mock(spec=[])
checker = CacheChecker(document_store=nonclosable_document_store, cache_field="url")
checker.close()
assert nonclosable_document_store.mock_calls == []
15 changes: 14 additions & 1 deletion test/components/caching/test_cache_checker_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: Apache-2.0

from unittest.mock import AsyncMock, MagicMock
from unittest.mock import AsyncMock, MagicMock, Mock

import pytest

Expand Down Expand Up @@ -60,3 +60,16 @@ async def test_run_async_filters_syntax(self):
await checker.run_async(items=["https://example.com/1"])
expected_filters = {"field": "url", "operator": "==", "value": "https://example.com/1"}
mock_store.filter_documents_async.assert_awaited_once_with(filters=expected_filters)

@pytest.mark.asyncio
async def test_close_async(self):
closable_document_store = Mock(spec=["close_async"])
closable_document_store.close_async = AsyncMock()
checker = CacheChecker(document_store=closable_document_store, cache_field="url")
await checker.close_async()
closable_document_store.close_async.assert_awaited_once_with()

nonclosable_document_store = Mock(spec=[])
checker = CacheChecker(document_store=nonclosable_document_store, cache_field="url")
await checker.close_async()
assert nonclosable_document_store.mock_calls == []
13 changes: 13 additions & 0 deletions test/components/retrievers/test_auto_merging_retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#
# SPDX-License-Identifier: Apache-2.0

from unittest.mock import Mock

import pytest

from haystack import Document, Pipeline
Expand Down Expand Up @@ -252,3 +254,14 @@ def test_run_go_up_hierarchy_multiple_levels_hit_root_document(self, in_memory_d

assert len(result["documents"]) == 1
assert result["documents"][0].meta["__level"] == 0 # hit root document

def test_close(self):
closable_document_store = Mock(spec=["close"])
retriever = AutoMergingRetriever(document_store=closable_document_store)
retriever.close()
closable_document_store.close.assert_called_once_with()

nonclosable_document_store = Mock(spec=[])
retriever = AutoMergingRetriever(document_store=nonclosable_document_store)
retriever.close()
assert nonclosable_document_store.mock_calls == []
15 changes: 15 additions & 0 deletions test/components/retrievers/test_auto_merging_retriever_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#
# SPDX-License-Identifier: Apache-2.0

from unittest.mock import AsyncMock, Mock

import pytest

from haystack import Document
Expand Down Expand Up @@ -206,3 +208,16 @@ async def test_run_go_up_hierarchy_multiple_levels_hit_root_document(self, in_me

assert len(result["documents"]) == 1
assert result["documents"][0].meta["__level"] == 0 # hit root document

@pytest.mark.asyncio
async def test_close_async(self):
closable_document_store = Mock(spec=["close_async"])
closable_document_store.close_async = AsyncMock()
retriever = AutoMergingRetriever(document_store=closable_document_store)
await retriever.close_async()
closable_document_store.close_async.assert_awaited_once_with()

nonclosable_document_store = Mock(spec=[])
retriever = AutoMergingRetriever(document_store=nonclosable_document_store)
await retriever.close_async()
assert nonclosable_document_store.mock_calls == []
12 changes: 12 additions & 0 deletions test/components/retrievers/test_filter_retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: Apache-2.0

from typing import Any
from unittest.mock import Mock

import pytest

Expand Down Expand Up @@ -144,3 +145,14 @@ def test_run_with_pipeline(self, sample_document_store, sample_docs):
results_docs = result["retriever"]["documents"]
assert results_docs
assert TestFilterRetriever._documents_equal(results_docs, sample_docs["en_docs"])

def test_close(self):
closable_document_store = Mock(spec=["close"])
retriever = FilterRetriever(document_store=closable_document_store)
retriever.close()
closable_document_store.close.assert_called_once_with()

nonclosable_document_store = Mock(spec=[])
retriever = FilterRetriever(document_store=nonclosable_document_store)
retriever.close()
assert nonclosable_document_store.mock_calls == []
14 changes: 14 additions & 0 deletions test/components/retrievers/test_filter_retriever_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: Apache-2.0

from typing import Any
from unittest.mock import AsyncMock, Mock

import pytest

Expand Down Expand Up @@ -94,3 +95,16 @@ async def test_run_with_pipeline(self, sample_document_store, sample_docs):
results_docs = result["retriever"]["documents"]
assert results_docs
assert TestFilterRetrieverAsync._documents_equal(results_docs, sample_docs["en_docs"])

@pytest.mark.asyncio
async def test_close_async(self):
closable_document_store = Mock(spec=["close_async"])
closable_document_store.close_async = AsyncMock()
retriever = FilterRetriever(document_store=closable_document_store)
await retriever.close_async()
closable_document_store.close_async.assert_awaited_once_with()

nonclosable_document_store = Mock(spec=[])
retriever = FilterRetriever(document_store=nonclosable_document_store)
await retriever.close_async()
assert nonclosable_document_store.mock_calls == []
13 changes: 12 additions & 1 deletion test/components/retrievers/test_sentence_window_retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import random
import re
from unittest.mock import ANY
from unittest.mock import ANY, Mock

import pytest

Expand Down Expand Up @@ -329,3 +329,14 @@ def test_serialization_deserialization_in_pipeline(self, in_memory_doc_store):
deserialized = Pipeline.from_dict(serialized)

assert deserialized == pipe

def test_close(self):
closable_document_store = Mock(spec=["close"])
retriever = SentenceWindowRetriever(document_store=closable_document_store)
retriever.close()
closable_document_store.close.assert_called_once_with()

nonclosable_document_store = Mock(spec=[])
retriever = SentenceWindowRetriever(document_store=nonclosable_document_store)
retriever.close()
assert nonclosable_document_store.mock_calls == []
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import random
import re
from unittest.mock import AsyncMock, Mock

import pytest

Expand Down Expand Up @@ -224,3 +225,16 @@ async def test_serialization_deserialization_in_pipeline(self, in_memory_doc_sto
deserialized = Pipeline.from_dict(serialized)

assert deserialized == pipe

@pytest.mark.asyncio
async def test_close_async(self):
closable_document_store = Mock(spec=["close_async"])
closable_document_store.close_async = AsyncMock()
retriever = SentenceWindowRetriever(document_store=closable_document_store)
await retriever.close_async()
closable_document_store.close_async.assert_awaited_once_with()

nonclosable_document_store = Mock(spec=[])
retriever = SentenceWindowRetriever(document_store=nonclosable_document_store)
await retriever.close_async()
assert nonclosable_document_store.mock_calls == []
26 changes: 26 additions & 0 deletions test/components/writers/test_document_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#
# SPDX-License-Identifier: Apache-2.0

from unittest.mock import AsyncMock, Mock

import pytest

from haystack import Document
Expand Down Expand Up @@ -107,6 +109,17 @@ def test_run_skip_policy(self, in_memory_doc_store):
result = writer.run(documents=documents)
assert result["documents_written"] == 0

def test_close(self):
closable_document_store = Mock(spec=["close"])
writer = DocumentWriter(document_store=closable_document_store)
writer.close()
closable_document_store.close.assert_called_once_with()

nonclosable_document_store = Mock(spec=[])
writer = DocumentWriter(document_store=nonclosable_document_store)
writer.close()
assert nonclosable_document_store.mock_calls == []

@pytest.mark.asyncio
async def test_run_async_invalid_docstore(self):
mocked_docstore_class = document_store_class("MockedDocumentStore")
Expand Down Expand Up @@ -144,3 +157,16 @@ async def test_run_async_skip_policy(self, in_memory_doc_store):

result = await writer.run_async(documents=documents)
assert result["documents_written"] == 0

@pytest.mark.asyncio
async def test_close_async(self):
closable_document_store = Mock(spec=["close_async"])
closable_document_store.close_async = AsyncMock()
writer = DocumentWriter(document_store=closable_document_store)
await writer.close_async()
closable_document_store.close_async.assert_awaited_once_with()

nonclosable_document_store = Mock(spec=[])
writer = DocumentWriter(document_store=nonclosable_document_store)
await writer.close_async()
assert nonclosable_document_store.mock_calls == []
Loading