weaviate-haystack 7.8.0__tar.gz → 8.0.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 (33) hide show
  1. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/CHANGELOG.md +70 -14
  2. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/PKG-INFO +3 -4
  3. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/docker-compose.yml +14 -2
  4. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/pyproject.toml +2 -3
  5. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/weaviate/embedding_retriever.py +4 -4
  6. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/weaviate/_filters.py +57 -39
  7. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/weaviate/document_store.py +57 -6
  8. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_bm25_retriever.py +20 -5
  9. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_bm25_retriever_async.py +4 -5
  10. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_document_store.py +212 -104
  11. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_embedding_retriever.py +45 -7
  12. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_embedding_retriever_async.py +29 -6
  13. weaviate_haystack-8.0.0/tests/test_filters.py +191 -0
  14. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_hybrid_retriever.py +32 -16
  15. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_hybrid_retriever_async.py +7 -8
  16. weaviate_haystack-8.0.0/tests/test_retrieval.py +125 -0
  17. weaviate_haystack-7.8.0/tests/test_filters.py +0 -72
  18. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/.gitignore +0 -0
  19. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/LICENSE.txt +0 -0
  20. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/README.md +0 -0
  21. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/pydoc/config_docusaurus.yml +0 -0
  22. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
  23. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/weaviate/__init__.py +0 -0
  24. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/weaviate/bm25_retriever.py +0 -0
  25. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/components/retrievers/weaviate/hybrid_retriever.py +0 -0
  26. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/py.typed +0 -0
  27. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/weaviate/__init__.py +0 -0
  28. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/src/haystack_integrations/document_stores/weaviate/auth.py +0 -0
  29. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/__init__.py +0 -0
  30. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/conftest.py +0 -0
  31. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_auth.py +0 -0
  32. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_document_store_async.py +0 -0
  33. {weaviate_haystack-7.8.0 → weaviate_haystack-8.0.0}/tests/test_files/robot1.jpg +0 -0
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/weaviate-v7.9.0] - 2026-09-21
4
+
5
+ ### 🚀 Features
6
+
7
+ - Send X-Weaviate-Client-Integration telemetry header (#3954)
8
+
9
+ ### ⚙️ CI
10
+
11
+ - Improve changelog generation; fix existing changelogs (#3883)
12
+
13
+ ### 🧹 Chores
14
+
15
+ - Raise weaviate-client floor and test against Weaviate 1.39 (#3956)
16
+
17
+
18
+ ## [integrations/weaviate-v7.8.0] - 2026-09-01
19
+
20
+ ### 🐛 Bug Fixes
21
+
22
+ - `Weaviate` match `search_term` against metadata field value, not content + default size value set to 10 (#3643)
23
+ - Fix new issues raised by ruff 0.16.0 (#3670)
24
+ - `WeaviateDocumentStore` get_metadata_field_unique_values setting list of values to return to `Any` (#3708)
25
+ - Weaviate adding optional parameter `filters` to `get_metadata_field_unique_values()` (#3741)
26
+ - Standardize license files (#3771)
27
+
28
+ ### 🧪 Testing
29
+
30
+ - `WeaviateDocumentStore` removing duplicated tests already covered by haystack Mixin tests (#3787)
31
+
32
+
3
33
  ## [integrations/weaviate-v7.7.0] - 2026-07-22
4
34
 
5
35
  ### 🚜 Refactor
@@ -207,21 +237,20 @@
207
237
 
208
238
  ### 🧹 Chores
209
239
 
210
- - Weaviate - remove legacy filter support (#1070)
211
240
  - Update changelog after removing legacy filters (#1083)
212
241
  - Update ruff linting scripts and settings (#1105)
213
242
 
243
+ ## [integrations/weaviate-v3.0.0] - 2024-09-12
214
244
 
215
- ## [integrations/weaviate-v2.2.1] - 2024-09-07
245
+ ### 🧹 Chores
216
246
 
217
- ### 🚀 Features
247
+ - Weaviate - remove legacy filter support (#1070)
218
248
 
219
- - Add filter_policy to weaviate integration (#824)
249
+
250
+ ## [integrations/weaviate-v2.2.1] - 2024-09-07
220
251
 
221
252
  ### 🐛 Bug Fixes
222
253
 
223
- - Weaviate filter error (#811)
224
- - Fix connection to Weaviate Cloud Service (#624)
225
254
  - Pin weaviate-client (#1046)
226
255
  - Weaviate - fix connection issues with some WCS URLs (#1058)
227
256
 
@@ -229,9 +258,27 @@
229
258
 
230
259
  - Do not retry tests in `hatch run test` command (#954)
231
260
 
232
- ### ⚙️ CI
261
+ ### 🌀 Miscellaneous
233
262
 
234
- - Retry tests to reduce flakyness (#836)
263
+ - Chore: weaviate - ruff update, don't ruff tests (#986)
264
+
265
+ ## [integrations/weaviate-v2.2.0] - 2024-08-02
266
+
267
+ ### 🌀 Miscellaneous
268
+
269
+ - Fix: Weaviate: Use collections.list_all instead of collections._get_all (#921)
270
+
271
+ ## [integrations/weaviate-v2.1.3] - 2024-07-17
272
+
273
+ ### 🌀 Miscellaneous
274
+
275
+ - Fix: `weaviate` - Fallback to default filter policy when deserializing retrievers without the init parameter (#903)
276
+
277
+ ## [integrations/weaviate-v2.1.2] - 2024-07-15
278
+
279
+ ### 🚀 Features
280
+
281
+ - Add filter_policy to weaviate integration (#824)
235
282
 
236
283
  ### 🧹 Chores
237
284
 
@@ -239,15 +286,24 @@
239
286
 
240
287
  ### 🌀 Miscellaneous
241
288
 
289
+ - Chore: Minor retriever pydoc fix (#884)
290
+
291
+ ## [integrations/weaviate-v2.1.1] - 2024-06-25
292
+
293
+ ### 🐛 Bug Fixes
294
+
295
+ - Weaviate filter error (#811)
296
+ - Fix connection to Weaviate Cloud Service (#624)
297
+
298
+ ### ⚙️ CI
299
+
300
+ - Retry tests to reduce flakyness (#836)
301
+
302
+ ### 🌀 Miscellaneous
303
+
242
304
  - Handle connection to WCS and add tests
243
305
  - Revert "Handle connection to WCS and add tests"
244
-
245
- This reverts commit f48802b2ce612896fd06a13cf33dffd9f77a8859.
246
306
  - Ci: install `pytest-rerunfailures` where needed; add retry config to `test-cov` script (#845)
247
- - Chore: Minor retriever pydoc fix (#884)
248
- - Fix: `weaviate` - Fallback to default filter policy when deserializing retrievers without the init parameter (#903)
249
- - Fix: Weaviate: Use collections.list_all instead of collections._get_all (#921)
250
- - Chore: weaviate - ruff update, don't ruff tests (#986)
251
307
 
252
308
  ## [integrations/weaviate-v2.1.0] - 2024-06-10
253
309
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: weaviate-haystack
3
- Version: 7.8.0
3
+ Version: 8.0.0
4
4
  Summary: An integration of Weaviate vector database 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/weaviate/README.md
@@ -11,17 +11,16 @@ License-File: LICENSE.txt
11
11
  Classifier: Development Status :: 4 - Beta
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Programming Language :: Python
14
- Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Programming Language :: Python :: 3.13
18
17
  Classifier: Programming Language :: Python :: 3.14
19
18
  Classifier: Programming Language :: Python :: Implementation :: CPython
20
19
  Classifier: Programming Language :: Python :: Implementation :: PyPy
21
- Requires-Python: >=3.10
20
+ Requires-Python: >=3.11
22
21
  Requires-Dist: haystack-ai>=2.28.0
23
22
  Requires-Dist: python-dateutil>=2.7.0
24
- Requires-Dist: weaviate-client>=4.20
23
+ Requires-Dist: weaviate-client>=4.23.0
25
24
  Description-Content-Type: text/markdown
26
25
 
27
26
  # weaviate-haystack
@@ -7,7 +7,7 @@ services:
7
7
  - '8080'
8
8
  - --scheme
9
9
  - http
10
- image: semitechnologies/weaviate:1.36.2
10
+ image: cr.weaviate.io/semitechnologies/weaviate:1.39.4
11
11
  ports:
12
12
  - 8080:8080
13
13
  - 50051:50051
@@ -18,4 +18,16 @@ services:
18
18
  PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
19
19
  DEFAULT_VECTORIZER_MODULE: 'none'
20
20
  ENABLE_MODULES: ''
21
- CLUSTER_HOSTNAME: 'node1'
21
+ CLUSTER_HOSTNAME: 'node1'
22
+ healthcheck:
23
+ test:
24
+ - CMD
25
+ - busybox
26
+ - wget
27
+ - -q
28
+ - -O
29
+ - '-'
30
+ - http://127.0.0.1:8080/v1/.well-known/ready
31
+ interval: 5s
32
+ timeout: 3s
33
+ retries: 20
@@ -7,7 +7,7 @@ name = "weaviate-haystack"
7
7
  dynamic = ["version"]
8
8
  description = "An integration of Weaviate vector database with Haystack"
9
9
  readme = "README.md"
10
- requires-python = ">=3.10"
10
+ requires-python = ">=3.11"
11
11
  license = "Apache-2.0"
12
12
  keywords = []
13
13
  authors = [{ name = "deepset GmbH", email = "info@deepset.ai" }]
@@ -15,7 +15,6 @@ classifiers = [
15
15
  "License :: OSI Approved :: Apache Software License",
16
16
  "Development Status :: 4 - Beta",
17
17
  "Programming Language :: Python",
18
- "Programming Language :: Python :: 3.10",
19
18
  "Programming Language :: Python :: 3.11",
20
19
  "Programming Language :: Python :: 3.12",
21
20
  "Programming Language :: Python :: 3.13",
@@ -25,7 +24,7 @@ classifiers = [
25
24
  ]
26
25
  dependencies = [
27
26
  "haystack-ai>=2.28.0",
28
- "weaviate-client>=4.20",
27
+ "weaviate-client>=4.23.0",
29
28
  "python-dateutil>=2.7.0",
30
29
  ]
31
30
 
@@ -143,8 +143,8 @@ class WeaviateEmbeddingRetriever:
143
143
  filters = apply_filter_policy(self._filter_policy, self._filters, filters)
144
144
  top_k = top_k or self._top_k
145
145
 
146
- distance = distance or self._distance
147
- certainty = certainty or self._certainty
146
+ distance = self._distance if distance is None else distance
147
+ certainty = self._certainty if certainty is None else certainty
148
148
  if distance is not None and certainty is not None:
149
149
  msg = f"Can't use 'distance' ({distance}) and 'certainty' ({certainty}) parameters together"
150
150
  raise ValueError(msg)
@@ -191,8 +191,8 @@ class WeaviateEmbeddingRetriever:
191
191
  filters = apply_filter_policy(self._filter_policy, self._filters, filters)
192
192
  top_k = top_k or self._top_k
193
193
 
194
- distance = distance or self._distance
195
- certainty = certainty or self._certainty
194
+ distance = self._distance if distance is None else distance
195
+ certainty = self._certainty if certainty is None else certainty
196
196
  if distance is not None and certainty is not None:
197
197
  msg = f"Can't use 'distance' ({distance}) and 'certainty' ({certainty}) parameters together"
198
198
  raise ValueError(msg)
@@ -33,6 +33,10 @@ def convert_filters(filters: dict[str, Any]) -> FilterReturn:
33
33
  Note: The ``contains`` operator performs substring matching and is
34
34
  **case-sensitive**. For case-insensitive matching, normalize the value
35
35
  (e.g., lowercase) before building the filter.
36
+
37
+ Note: ``NOT`` is applied client-side by inverting its conditions (De Morgan), so it works
38
+ on any Weaviate version. The only exception is ``NOT`` over ``contains``, which has no
39
+ invertible counterpart and uses Weaviate's native ``NOT`` operator (Weaviate 1.33 or later).
36
40
  """
37
41
  if not isinstance(filters, dict):
38
42
  msg = "Filters must be a dictionary"
@@ -43,7 +47,13 @@ def convert_filters(filters: dict[str, Any]) -> FilterReturn:
43
47
  return _parse_logical_condition(filters)
44
48
 
45
49
 
46
- OPERATOR_INVERSE = {
50
+ LOGICAL_OPERATORS = {
51
+ "AND": Filter.all_of,
52
+ "OR": Filter.any_of,
53
+ }
54
+
55
+
56
+ COMPARISON_INVERSE = {
47
57
  "==": "!=",
48
58
  "!=": "==",
49
59
  ">": "<=",
@@ -52,38 +62,10 @@ OPERATOR_INVERSE = {
52
62
  "<=": ">",
53
63
  "in": "not in",
54
64
  "not in": "in",
55
- "AND": "OR",
56
- "OR": "AND",
57
- "NOT": "OR",
58
- }
59
-
60
-
61
- def _invert_condition(filters: dict[str, Any]) -> dict[str, Any]:
62
- """
63
- Invert condition recursively.
64
-
65
- Weaviate doesn't support NOT filters so we need to invert them ourselves.
66
- """
67
- inverted_condition = filters.copy()
68
- if "operator" not in filters:
69
- # Let's not handle this stuff in here, we'll fail later on anyway.
70
- return inverted_condition
71
- inverted_condition["operator"] = OPERATOR_INVERSE[filters["operator"]]
72
- if "conditions" in filters:
73
- inverted_condition["conditions"] = []
74
- for condition in filters["conditions"]:
75
- inverted_condition["conditions"].append(_invert_condition(condition))
76
-
77
- return inverted_condition
78
-
79
-
80
- LOGICAL_OPERATORS = {
81
- "AND": Filter.all_of,
82
- "OR": Filter.any_of,
83
65
  }
84
66
 
85
67
 
86
- def _parse_logical_condition(condition: dict[str, Any]) -> FilterReturn:
68
+ def _validate_logical_condition(condition: dict[str, Any]) -> None:
87
69
  if "operator" not in condition:
88
70
  msg = f"'operator' key missing in {condition}"
89
71
  raise FilterError(msg)
@@ -91,23 +73,55 @@ def _parse_logical_condition(condition: dict[str, Any]) -> FilterReturn:
91
73
  msg = f"'conditions' key missing in {condition}"
92
74
  raise FilterError(msg)
93
75
 
76
+
77
+ def _parse_logical_condition(condition: dict[str, Any]) -> FilterReturn:
78
+ _validate_logical_condition(condition)
79
+
94
80
  operator = condition["operator"]
95
81
  if operator in ["AND", "OR"]:
96
- operands = []
97
- for c in condition["conditions"]:
98
- if "field" not in c:
99
- operands.append(_parse_logical_condition(c))
100
- else:
101
- operands.append(_parse_comparison_condition(c))
102
- return LOGICAL_OPERATORS[operator](operands)
82
+ return LOGICAL_OPERATORS[operator](_parse_operands(condition["conditions"]))
83
+ elif operator == "NOT":
84
+ # A NOT node negates the conjunction of its conditions: NOT(a AND b) == NOT(a) OR NOT(b).
85
+ return Filter.any_of([_negate(c) for c in condition["conditions"]])
86
+ else:
87
+ msg = f"Unknown logical operator '{operator}'"
88
+ raise FilterError(msg)
89
+
90
+
91
+ def _negate(condition: dict[str, Any]) -> FilterReturn:
92
+ """
93
+ Build the negation of a condition by applying De Morgan's laws.
94
+
95
+ Negation is done client-side so that it works on Weaviate versions without a native NOT operator.
96
+ """
97
+ if "field" in condition:
98
+ operator = condition.get("operator")
99
+ if operator in COMPARISON_INVERSE:
100
+ return _parse_comparison_condition({**condition, "operator": COMPARISON_INVERSE[operator]})
101
+ # Operators like `contains` have no invertible counterpart, so we fall back to Weaviate's
102
+ # native NOT, which requires Weaviate 1.33 or later.
103
+ # Unknown operators raise a FilterError while parsing the condition.
104
+ return Filter.not_(_parse_comparison_condition(condition))
105
+
106
+ _validate_logical_condition(condition)
107
+ operator = condition["operator"]
108
+ conditions = condition["conditions"]
109
+ if operator == "AND":
110
+ return Filter.any_of([_negate(c) for c in conditions])
111
+ elif operator == "OR":
112
+ return Filter.all_of([_negate(c) for c in conditions])
103
113
  elif operator == "NOT":
104
- inverted_conditions = _invert_condition(condition)
105
- return _parse_logical_condition(inverted_conditions)
114
+ # The two negations cancel out, leaving the conjunction of the conditions.
115
+ return Filter.all_of(_parse_operands(conditions))
106
116
  else:
107
117
  msg = f"Unknown logical operator '{operator}'"
108
118
  raise FilterError(msg)
109
119
 
110
120
 
121
+ def _parse_operands(conditions: list[dict[str, Any]]) -> list[FilterReturn]:
122
+ return [_parse_comparison_condition(c) if "field" in c else _parse_logical_condition(c) for c in conditions]
123
+
124
+
111
125
  def _handle_date(value: Any) -> str:
112
126
  if isinstance(value, str):
113
127
  try:
@@ -282,6 +296,10 @@ def _parse_comparison_condition(condition: dict[str, Any]) -> FilterReturn:
282
296
  operator: str = condition["operator"]
283
297
  value: Any = condition["value"]
284
298
 
299
+ if operator not in COMPARISON_OPERATORS:
300
+ msg = f"Unknown comparison operator '{operator}'. Valid operators are: {list(COMPARISON_OPERATORS.keys())}"
301
+ raise FilterError(msg)
302
+
285
303
  return COMPARISON_OPERATORS[operator](field, value)
286
304
 
287
305
 
@@ -3,7 +3,8 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
  import base64
6
- import datetime
6
+ import datetime as dt
7
+ import importlib.metadata
7
8
  import json
8
9
  from contextlib import suppress
9
10
  from dataclasses import asdict
@@ -38,6 +39,27 @@ from .auth import AuthCredentials
38
39
 
39
40
  logger = logging.getLogger(__name__)
40
41
 
42
+ # Weaviate tracks which framework integrations talk to it through this header, so that adoption of
43
+ # weaviate-haystack shows up in Weaviate's own telemetry. See https://github.com/weaviate/weaviate/pull/10535.
44
+ _INTEGRATION_HEADER = "X-Weaviate-Client-Integration"
45
+ _INTEGRATION_NAME = "haystack-python"
46
+ _PACKAGE_NAME = "weaviate-haystack"
47
+
48
+
49
+ def _integration_header_value() -> str:
50
+ """
51
+ Builds the value of the Weaviate client integration header.
52
+
53
+ :returns:
54
+ A `<name>/<version>` string. The version falls back to `unknown` when the package metadata is
55
+ unavailable, for example when running from a source checkout that was never installed.
56
+ """
57
+ try:
58
+ version = importlib.metadata.version(_PACKAGE_NAME)
59
+ except importlib.metadata.PackageNotFoundError:
60
+ version = "unknown"
61
+ return f"{_INTEGRATION_NAME}/{version}"
62
+
41
63
 
42
64
  # This is the default collection properties for Weaviate.
43
65
  # It's a list of properties that will be created on the collection.
@@ -144,6 +166,9 @@ class WeaviateDocumentStore:
144
166
  ```
145
167
  {"X-OpenAI-Api-Key": "<THE-KEY>"}, {"X-HuggingFace-Api-Key": "<THE-KEY>"}
146
168
  ```
169
+ Every connection also carries `X-Weaviate-Client-Integration: haystack-python/<version>`, which
170
+ identifies this integration in Weaviate's telemetry. Supplying that header here, in any casing,
171
+ overrides it.
147
172
  :param embedded_options:
148
173
  If set, create an embedded Weaviate cluster inside the client. For a full list of options see
149
174
  `weaviate.embedded.EmbeddedOptions`.
@@ -183,6 +208,26 @@ class WeaviateDocumentStore:
183
208
  "properties", DOCUMENT_COLLECTION_PROPERTIES
184
209
  )
185
210
 
211
+ def _connection_headers(self) -> dict[str, Any]:
212
+ """
213
+ Builds the headers passed to the Weaviate client.
214
+
215
+ Merges the integration identification header into the user supplied `additional_headers`. A user
216
+ supplied value for the same header wins, so the header can be overridden or suppressed. The merged
217
+ headers are deliberately not stored on the instance: `to_dict()` must keep serializing only the
218
+ headers the user actually passed, so that a serialized pipeline does not bake in the version of
219
+ `weaviate-haystack` that happened to write it.
220
+
221
+ :returns:
222
+ The headers to pass to the Weaviate client.
223
+ """
224
+ headers: dict[str, Any] = dict(self._additional_headers) if self._additional_headers else {}
225
+ # The comparison is case insensitive because gRPC metadata keys are case sensitive: a user supplied
226
+ # "x-weaviate-client-integration" would otherwise end up alongside our own spelling of the header.
227
+ if not any(key.lower() == _INTEGRATION_HEADER.lower() for key in headers):
228
+ headers[_INTEGRATION_HEADER] = _integration_header_value()
229
+ return headers
230
+
186
231
  @property
187
232
  def client(self) -> weaviate.WeaviateClient:
188
233
  """Return the synchronous Weaviate client, creating and connecting it if necessary."""
@@ -199,7 +244,7 @@ class WeaviateDocumentStore:
199
244
  self._client = weaviate.connect_to_weaviate_cloud(
200
245
  self._url,
201
246
  auth_credentials=self._auth_client_secret.resolve_value(),
202
- headers=self._additional_headers,
247
+ headers=self._connection_headers(),
203
248
  additional_config=self._additional_config,
204
249
  )
205
250
  else:
@@ -216,7 +261,7 @@ class WeaviateDocumentStore:
216
261
  ),
217
262
  auth_client_secret=self._auth_client_secret.resolve_value() if self._auth_client_secret else None,
218
263
  additional_config=self._additional_config,
219
- additional_headers=self._additional_headers,
264
+ additional_headers=self._connection_headers(),
220
265
  embedded_options=self._embedded_options,
221
266
  skip_init_checks=False,
222
267
  )
@@ -244,7 +289,7 @@ class WeaviateDocumentStore:
244
289
  self._async_client = weaviate.use_async_with_weaviate_cloud(
245
290
  self._url,
246
291
  auth_credentials=self._auth_client_secret.resolve_value(),
247
- headers=self._additional_headers,
292
+ headers=self._connection_headers(),
248
293
  additional_config=self._additional_config,
249
294
  )
250
295
  else:
@@ -261,7 +306,7 @@ class WeaviateDocumentStore:
261
306
  ),
262
307
  auth_client_secret=self._auth_client_secret.resolve_value() if self._auth_client_secret else None,
263
308
  additional_config=self._additional_config,
264
- additional_headers=self._additional_headers,
309
+ additional_headers=self._connection_headers(),
265
310
  embedded_options=self._embedded_options,
266
311
  skip_init_checks=False,
267
312
  )
@@ -831,7 +876,7 @@ class WeaviateDocumentStore:
831
876
  document_data.pop("blob_mime_type", None)
832
877
 
833
878
  for key, value in document_data.items():
834
- if isinstance(value, datetime.datetime):
879
+ if isinstance(value, dt.datetime):
835
880
  document_data[key] = value.strftime("%Y-%m-%dT%H:%M:%SZ")
836
881
 
837
882
  if weaviate_meta := getattr(data, "metadata", None):
@@ -938,6 +983,9 @@ class WeaviateDocumentStore:
938
983
  matching). For case-insensitive matching, normalize the value before
939
984
  building the filter.
940
985
 
986
+ Note: ``NOT`` over the ``contains`` operator is translated to Weaviate's
987
+ native ``NOT``, which requires Weaviate 1.33 or later.
988
+
941
989
  :param filters: The filters to apply to the document list.
942
990
  :returns: A list of Documents that match the given filters.
943
991
  """
@@ -961,6 +1009,9 @@ class WeaviateDocumentStore:
961
1009
  matching). For case-insensitive matching, normalize the value before
962
1010
  building the filter.
963
1011
 
1012
+ Note: ``NOT`` over the ``contains`` operator is translated to Weaviate's
1013
+ native ``NOT``, which requires Weaviate 1.33 or later.
1014
+
964
1015
  :param filters: The filters to apply to the document list.
965
1016
  :returns: A list of Documents that match the given filters.
966
1017
  """
@@ -2,9 +2,10 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from unittest.mock import Mock, patch
5
+ from unittest.mock import create_autospec, patch
6
6
 
7
7
  import pytest
8
+ from haystack import Pipeline
8
9
  from haystack.document_stores.types import FilterPolicy
9
10
 
10
11
  from haystack_integrations.components.retrievers.weaviate import WeaviateBM25Retriever
@@ -12,7 +13,7 @@ from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore
12
13
 
13
14
 
14
15
  def test_close():
15
- mock_document_store = Mock(spec=WeaviateDocumentStore)
16
+ mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
16
17
  retriever = WeaviateBM25Retriever(document_store=mock_document_store)
17
18
 
18
19
  retriever.close()
@@ -22,7 +23,7 @@ def test_close():
22
23
 
23
24
 
24
25
  def test_init_default():
25
- mock_document_store = Mock(spec=WeaviateDocumentStore)
26
+ mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
26
27
  retriever = WeaviateBM25Retriever(document_store=mock_document_store)
27
28
  assert retriever._document_store == mock_document_store
28
29
  assert retriever._filters == {}
@@ -153,10 +154,24 @@ def test_from_dict_no_filter_policy(_mock_weaviate):
153
154
  assert retriever._filter_policy == FilterPolicy.REPLACE
154
155
 
155
156
 
156
- @patch("haystack_integrations.components.retrievers.weaviate.bm25_retriever.WeaviateDocumentStore")
157
- def test_run(mock_document_store):
157
+ def test_run():
158
+ mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
158
159
  retriever = WeaviateBM25Retriever(document_store=mock_document_store)
159
160
  query = "some query"
160
161
  filters = {"field": "content", "operator": "==", "value": "Some text"}
161
162
  retriever.run(query=query, filters=filters, top_k=5)
162
163
  mock_document_store._bm25_retrieval.assert_called_once_with(query=query, filters=filters, top_k=5)
164
+
165
+
166
+ def test_pipeline_serde():
167
+ """A pipeline holding the retriever must survive a dumps/loads round trip, nested store included."""
168
+ document_store = WeaviateDocumentStore(url="http://localhost:8080")
169
+ pipeline = Pipeline()
170
+ pipeline.add_component("retriever", WeaviateBM25Retriever(document_store=document_store, top_k=3))
171
+
172
+ reloaded = Pipeline.loads(pipeline.dumps()).get_component("retriever")
173
+
174
+ assert isinstance(reloaded, WeaviateBM25Retriever)
175
+ assert isinstance(reloaded._document_store, WeaviateDocumentStore)
176
+ assert reloaded._document_store.to_dict() == document_store.to_dict()
177
+ assert reloaded._top_k == 3
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from unittest.mock import AsyncMock, Mock
5
+ from unittest.mock import create_autospec
6
6
 
7
7
  import pytest
8
8
 
@@ -12,8 +12,7 @@ from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore
12
12
 
13
13
  @pytest.mark.asyncio
14
14
  async def test_close_async():
15
- mock_document_store = Mock(spec=WeaviateDocumentStore)
16
- mock_document_store.close_async = AsyncMock()
15
+ mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
17
16
  retriever = WeaviateBM25Retriever(document_store=mock_document_store)
18
17
 
19
18
  await retriever.close_async()
@@ -24,8 +23,8 @@ async def test_close_async():
24
23
 
25
24
  @pytest.mark.asyncio
26
25
  async def test_run_async_calls_async_retrieval():
27
- mock_document_store = Mock(spec=WeaviateDocumentStore)
28
- mock_document_store._bm25_retrieval_async = AsyncMock(return_value=[])
26
+ mock_document_store = create_autospec(WeaviateDocumentStore, instance=True)
27
+ mock_document_store._bm25_retrieval_async.return_value = []
29
28
 
30
29
  retriever = WeaviateBM25Retriever(document_store=mock_document_store)
31
30