pgvector-haystack 6.3.1__tar.gz → 6.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/CHANGELOG.md +24 -0
  2. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/PKG-INFO +1 -1
  3. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/examples/embedding_retrieval.py +4 -1
  4. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/examples/hybrid_retrieval.py +4 -1
  5. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/src/haystack_integrations/components/retrievers/pgvector/embedding_retriever.py +15 -2
  6. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/src/haystack_integrations/components/retrievers/pgvector/keyword_retriever.py +12 -0
  7. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/src/haystack_integrations/document_stores/pgvector/document_store.py +23 -0
  8. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/src/haystack_integrations/document_stores/pgvector/filters.py +2 -2
  9. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/tests/test_document_store.py +39 -0
  10. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/tests/test_document_store_async.py +43 -1
  11. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/tests/test_filters.py +12 -0
  12. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/tests/test_retrievers.py +41 -1
  13. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/.gitignore +0 -0
  14. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/LICENSE.txt +0 -0
  15. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/README.md +0 -0
  16. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/pydoc/config_docusaurus.yml +0 -0
  17. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/pyproject.toml +0 -0
  18. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/src/haystack_integrations/components/retrievers/pgvector/__init__.py +0 -0
  19. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
  20. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/src/haystack_integrations/document_stores/pgvector/__init__.py +0 -0
  21. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/src/haystack_integrations/document_stores/pgvector/converters.py +0 -0
  22. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/src/haystack_integrations/document_stores/py.typed +0 -0
  23. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/tests/__init__.py +0 -0
  24. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/tests/conftest.py +0 -0
  25. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/tests/test_converters.py +0 -0
  26. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/tests/test_retrieval.py +0 -0
  27. {pgvector_haystack-6.3.1 → pgvector_haystack-6.4.0}/tests/test_retrieval_async.py +0 -0
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/pgvector-v6.3.1] - 2026-06-01
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - Replace in place dataclass mutations for pgvector #11087 (#3142)
8
+ - *(pgvector)* Order retrieval by distance operator (#3370)
9
+
10
+ ### 🚜 Refactor
11
+
12
+ - *(pgvector)* Use async DocumentStore mixin tests (#3094)
13
+
14
+ ### 🧪 Testing
15
+
16
+ - `PGVectorDocumentStore`e use Mixin tests (#3003)
17
+ - Test compatible integrations with python 3.14; update pyproject (#3001)
18
+ - Track test coverage for all integrations (#3065)
19
+ - Add tests for pgvector document store (#3156)
20
+
21
+ ### 🧹 Chores
22
+
23
+ - Add ANN ruff ruleset to optimum, paddleocr, pgvector, pinecone, pyversity, qdrant, ragas, snowflake (#2992)
24
+ - Enforce ruff docstring rules in integrations 31-40 (openrouter, opensearch, optimum, paddleocr, pgvector, pinecone, pyversity, qdrant, ragas, snowflake) (#3011)
25
+
26
+
3
27
  ## [integrations/pgvector-v6.3.0] - 2026-03-17
4
28
 
5
29
  ### 🐛 Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgvector-haystack
3
- Version: 6.3.1
3
+ Version: 6.4.0
4
4
  Summary: An integration of pgvector (vector search extension for Postgres) with Haystack
5
5
  Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations
6
6
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/pgvector/README.md
@@ -14,10 +14,13 @@ import glob
14
14
 
15
15
  from haystack import Pipeline
16
16
  from haystack.components.converters import MarkdownToDocument
17
- from haystack.components.embedders import SentenceTransformersDocumentEmbedder, SentenceTransformersTextEmbedder
18
17
  from haystack.components.preprocessors import DocumentSplitter
19
18
  from haystack.components.writers import DocumentWriter
20
19
 
20
+ from haystack_integrations.components.embedders.sentence_transformers import (
21
+ SentenceTransformersDocumentEmbedder,
22
+ SentenceTransformersTextEmbedder,
23
+ )
21
24
  from haystack_integrations.components.retrievers.pgvector import PgvectorEmbeddingRetriever
22
25
  from haystack_integrations.document_stores.pgvector import PgvectorDocumentStore
23
26
 
@@ -14,11 +14,14 @@ import glob
14
14
 
15
15
  from haystack import Pipeline
16
16
  from haystack.components.converters import MarkdownToDocument
17
- from haystack.components.embedders import SentenceTransformersDocumentEmbedder, SentenceTransformersTextEmbedder
18
17
  from haystack.components.joiners import DocumentJoiner
19
18
  from haystack.components.preprocessors import DocumentSplitter
20
19
  from haystack.components.writers import DocumentWriter
21
20
 
21
+ from haystack_integrations.components.embedders.sentence_transformers import (
22
+ SentenceTransformersDocumentEmbedder,
23
+ SentenceTransformersTextEmbedder,
24
+ )
22
25
  from haystack_integrations.components.retrievers.pgvector import PgvectorEmbeddingRetriever, PgvectorKeywordRetriever
23
26
  from haystack_integrations.document_stores.pgvector import PgvectorDocumentStore
24
27
 
@@ -21,7 +21,9 @@ class PgvectorEmbeddingRetriever:
21
21
  ```python
22
22
  from haystack.document_stores import DuplicatePolicy
23
23
  from haystack import Document, Pipeline
24
- from haystack.components.embedders import SentenceTransformersTextEmbedder, SentenceTransformersDocumentEmbedder
24
+ # Requires: pip install sentence-transformers-haystack
25
+ from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersTextEmbedder
26
+ from haystack_integrations.components.embedders.sentence_transformers import SentenceTransformersDocumentEmbedder
25
27
 
26
28
  from haystack_integrations.document_stores.pgvector import PgvectorDocumentStore
27
29
  from haystack_integrations.components.retrievers.pgvector import PgvectorEmbeddingRetriever
@@ -40,7 +42,6 @@ class PgvectorEmbeddingRetriever:
40
42
  Document(content="In certain places, you can witness the phenomenon of bioluminescent waves.")]
41
43
 
42
44
  document_embedder = SentenceTransformersDocumentEmbedder()
43
- document_embedder.warm_up()
44
45
  documents_with_embeddings = document_embedder.run(documents)
45
46
 
46
47
  document_store.write_documents(documents_with_embeddings.get("documents"), policy=DuplicatePolicy.OVERWRITE)
@@ -135,6 +136,18 @@ class PgvectorEmbeddingRetriever:
135
136
  data["init_parameters"]["filter_policy"] = FilterPolicy.from_str(filter_policy)
136
137
  return default_from_dict(cls, data)
137
138
 
139
+ def close(self) -> None:
140
+ """
141
+ Release the synchronous resources of the underlying Document Store.
142
+ """
143
+ self.document_store.close()
144
+
145
+ async def close_async(self) -> None:
146
+ """
147
+ Release the asynchronous resources of the underlying Document Store.
148
+ """
149
+ await self.document_store.close_async()
150
+
138
151
  @component.output_types(documents=list[Document])
139
152
  def run(
140
153
  self,
@@ -109,6 +109,18 @@ class PgvectorKeywordRetriever:
109
109
  data["init_parameters"]["filter_policy"] = FilterPolicy.from_str(filter_policy)
110
110
  return default_from_dict(cls, data)
111
111
 
112
+ def close(self) -> None:
113
+ """
114
+ Release the synchronous resources of the underlying Document Store.
115
+ """
116
+ self.document_store.close()
117
+
118
+ async def close_async(self) -> None:
119
+ """
120
+ Release the asynchronous resources of the underlying Document Store.
121
+ """
122
+ await self.document_store.close_async()
123
+
112
124
  @component.output_types(documents=list[Document])
113
125
  def run(
114
126
  self,
@@ -2,6 +2,7 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ from contextlib import suppress
5
6
  from typing import Any, Literal, overload
6
7
 
7
8
  from haystack import default_from_dict, default_to_dict, logging
@@ -223,6 +224,28 @@ class PgvectorDocumentStore:
223
224
  deserialize_secrets_inplace(data["init_parameters"], ["connection_string"])
224
225
  return default_from_dict(cls, data)
225
226
 
227
+ def close(self) -> None:
228
+ """
229
+ Release the associated synchronous resources.
230
+ """
231
+ if self._connection is not None:
232
+ with suppress(Exception):
233
+ self._connection.close()
234
+ self._connection = None
235
+ self._cursor = None
236
+ self._dict_cursor = None
237
+
238
+ async def close_async(self) -> None:
239
+ """
240
+ Release the associated asynchronous resources.
241
+ """
242
+ if self._async_connection is not None:
243
+ with suppress(Exception):
244
+ await self._async_connection.close()
245
+ self._async_connection = None
246
+ self._async_cursor = None
247
+ self._async_dict_cursor = None
248
+
226
249
  @staticmethod
227
250
  def _connection_is_valid(connection: Connection) -> bool:
228
251
  """
@@ -20,7 +20,7 @@ PYTHON_TYPES_TO_PG_TYPES = {
20
20
  bool: "boolean",
21
21
  }
22
22
 
23
- NO_VALUE = "no_value"
23
+ NO_VALUE = object() # unique sentinel; can never equal a user-supplied filter value
24
24
 
25
25
 
26
26
  def _validate_filters(filters: dict[str, Any] | None = None) -> None:
@@ -48,7 +48,7 @@ def _convert_filters_to_where_clause_and_params(
48
48
  query, values = _parse_logical_condition(filters)
49
49
 
50
50
  where_clause = SQL(f" {operator} ") + query
51
- params = tuple(value for value in values if value != NO_VALUE)
51
+ params = tuple(value for value in values if value is not NO_VALUE)
52
52
 
53
53
  return where_clause, params
54
54
 
@@ -103,6 +103,18 @@ class TestDocumentStore(
103
103
  document_store._ensure_db_setup()
104
104
  assert "Failed to connect to PostgreSQL database" in str(e)
105
105
 
106
+ def test_close_and_reopen(self, document_store: PgvectorDocumentStore):
107
+ assert document_store.count_documents() == 0
108
+ assert document_store._connection is not None
109
+
110
+ document_store.close()
111
+ assert document_store._connection is None
112
+ assert document_store._cursor is None
113
+ assert document_store._dict_cursor is None
114
+
115
+ assert document_store.count_documents() == 0
116
+ assert document_store._connection is not None
117
+
106
118
 
107
119
  @pytest.mark.usefixtures("patches_for_unit_tests")
108
120
  def test_init(monkeypatch):
@@ -353,6 +365,33 @@ def test_process_count_unique_metadata_result_uses_zero_for_missing_keys():
353
365
  assert counts == {"category": 5, "language": 0}
354
366
 
355
367
 
368
+ def test_close(mock_store):
369
+ mock_connection = Mock(spec=Connection)
370
+ mock_store._connection = mock_connection
371
+ mock_store._cursor = Mock(spec=Cursor)
372
+ mock_store._dict_cursor = Mock(spec=Cursor)
373
+
374
+ mock_store.close()
375
+
376
+ mock_connection.close.assert_called_once()
377
+ assert mock_store._connection is None
378
+ assert mock_store._cursor is None
379
+ assert mock_store._dict_cursor is None
380
+
381
+ mock_store.close()
382
+ mock_connection.close.assert_called_once()
383
+
384
+
385
+ def test_close_is_exception_safe(mock_store):
386
+ mock_connection = Mock(spec=Connection)
387
+ mock_connection.close.side_effect = RuntimeError("boom")
388
+ mock_store._connection = mock_connection
389
+
390
+ mock_store.close()
391
+
392
+ assert mock_store._connection is None
393
+
394
+
356
395
  @pytest.mark.integration
357
396
  def test_halfvec_hnsw_write_documents(document_store_w_halfvec_hnsw_index: PgvectorDocumentStore):
358
397
  documents = [
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from unittest.mock import Mock, patch
5
+ from unittest.mock import AsyncMock, Mock, patch
6
6
 
7
7
  import psycopg
8
8
  import pytest
@@ -131,6 +131,18 @@ class TestDocumentStoreAsync(
131
131
  """
132
132
  await document_store.delete_table_async() # if throw error, test fails
133
133
 
134
+ async def test_close_async_and_reopen(self, document_store: PgvectorDocumentStore):
135
+ assert await document_store.count_documents_async() == 0
136
+ assert document_store._async_connection is not None
137
+
138
+ await document_store.close_async()
139
+ assert document_store._async_connection is None
140
+ assert document_store._async_cursor is None
141
+ assert document_store._async_dict_cursor is None
142
+
143
+ assert await document_store.count_documents_async() == 0
144
+ assert document_store._async_connection is not None
145
+
134
146
 
135
147
  @pytest.mark.integration
136
148
  @pytest.mark.asyncio
@@ -302,3 +314,33 @@ async def test_write_documents_async_rejects_non_document_items(mock_store):
302
314
  async def test_count_unique_metadata_by_filter_async_rejects_empty_fields(mock_store):
303
315
  with pytest.raises(ValueError, match="metadata_fields must be a non-empty list"):
304
316
  await mock_store.count_unique_metadata_by_filter_async(filters={}, metadata_fields=[])
317
+
318
+
319
+ @pytest.mark.asyncio
320
+ async def test_close_async(mock_store):
321
+ mock_connection = Mock(spec=AsyncConnection)
322
+ mock_connection.close = AsyncMock()
323
+ mock_store._async_connection = mock_connection
324
+ mock_store._async_cursor = Mock(spec=AsyncCursor)
325
+ mock_store._async_dict_cursor = Mock(spec=AsyncCursor)
326
+
327
+ await mock_store.close_async()
328
+
329
+ mock_connection.close.assert_awaited_once()
330
+ assert mock_store._async_connection is None
331
+ assert mock_store._async_cursor is None
332
+ assert mock_store._async_dict_cursor is None
333
+
334
+ await mock_store.close_async()
335
+ mock_connection.close.assert_awaited_once()
336
+
337
+
338
+ @pytest.mark.asyncio
339
+ async def test_close_async_is_exception_safe(mock_store):
340
+ mock_connection = Mock(spec=AsyncConnection)
341
+ mock_connection.close = AsyncMock(side_effect=RuntimeError("boom"))
342
+ mock_store._async_connection = mock_connection
343
+
344
+ await mock_store.close_async()
345
+
346
+ assert mock_store._async_connection is None
@@ -265,6 +265,18 @@ def test_convert_filters_to_where_clause_and_params_handle_null():
265
265
  assert params == ("intro",)
266
266
 
267
267
 
268
+ def test_convert_filters_to_where_clause_and_params_value_equal_to_no_value_sentinel():
269
+ """A legitimate equality filter whose value is the literal string the internal
270
+ NO_VALUE sentinel used to be ("no_value") must not be dropped from params --
271
+ the query still has a placeholder for it."""
272
+ filters = {"field": "meta.tag", "operator": "==", "value": "no_value"}
273
+ where_clause, params = _convert_filters_to_where_clause_and_params(filters)
274
+
275
+ expected_sql = " WHERE meta->>'tag' = %s"
276
+ assert _render(where_clause) == expected_sql
277
+ assert params == ("no_value",)
278
+
279
+
268
280
  def test_validate_filters():
269
281
  filters = {"field": "meta.number", "operator": "==", "value": 100}
270
282
  _validate_filters(filters)
@@ -1,7 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
- from unittest.mock import Mock
4
+ from unittest.mock import AsyncMock, Mock
5
5
 
6
6
  import pytest
7
7
  from haystack.dataclasses import Document
@@ -126,6 +126,26 @@ class TestEmbeddingRetriever:
126
126
  assert retriever.filter_policy == FilterPolicy.REPLACE
127
127
  assert retriever.vector_function == "l2_distance"
128
128
 
129
+ def test_close(self):
130
+ mock_store = Mock(spec=PgvectorDocumentStore)
131
+ retriever = PgvectorEmbeddingRetriever(document_store=mock_store, vector_function="l2_distance")
132
+
133
+ retriever.close()
134
+
135
+ mock_store.close.assert_called_once()
136
+ assert retriever.document_store is mock_store
137
+
138
+ @pytest.mark.asyncio
139
+ async def test_close_async(self):
140
+ mock_store = Mock(spec=PgvectorDocumentStore)
141
+ mock_store.close_async = AsyncMock()
142
+ retriever = PgvectorEmbeddingRetriever(document_store=mock_store, vector_function="l2_distance")
143
+
144
+ await retriever.close_async()
145
+
146
+ mock_store.close_async.assert_awaited_once()
147
+ assert retriever.document_store is mock_store
148
+
129
149
  def test_run(self):
130
150
  mock_store = Mock(spec=PgvectorDocumentStore)
131
151
  doc = Document(content="Test doc", embedding=[0.1, 0.2])
@@ -315,6 +335,26 @@ class TestKeywordRetriever:
315
335
  assert retriever.filter_policy == FilterPolicy.REPLACE # defaults to REPLACE
316
336
  assert retriever.top_k == 5
317
337
 
338
+ def test_close(self):
339
+ mock_store = Mock(spec=PgvectorDocumentStore)
340
+ retriever = PgvectorKeywordRetriever(document_store=mock_store)
341
+
342
+ retriever.close()
343
+
344
+ mock_store.close.assert_called_once()
345
+ assert retriever.document_store is mock_store
346
+
347
+ @pytest.mark.asyncio
348
+ async def test_close_async(self):
349
+ mock_store = Mock(spec=PgvectorDocumentStore)
350
+ mock_store.close_async = AsyncMock()
351
+ retriever = PgvectorKeywordRetriever(document_store=mock_store)
352
+
353
+ await retriever.close_async()
354
+
355
+ mock_store.close_async.assert_awaited_once()
356
+ assert retriever.document_store is mock_store
357
+
318
358
  def test_run(self):
319
359
  mock_store = Mock(spec=PgvectorDocumentStore)
320
360
  doc = Document(content="Test doc", embedding=[0.1, 0.2])