meilisearch-python-sdk 2.3.0__py3-none-any.whl → 2.4.0__py3-none-any.whl

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.

Potentially problematic release.


This version of meilisearch-python-sdk might be problematic. Click here for more details.

@@ -4,9 +4,9 @@ import asyncio
4
4
  import json
5
5
  from csv import DictReader
6
6
  from datetime import datetime
7
- from functools import partial
7
+ from functools import cached_property, partial
8
8
  from pathlib import Path
9
- from typing import Any, Generator, Mapping, Sequence
9
+ from typing import Any, Generator, MutableMapping, Sequence
10
10
  from urllib.parse import urlencode
11
11
  from warnings import warn
12
12
 
@@ -27,10 +27,22 @@ from meilisearch_python_sdk.models.settings import (
27
27
  TypoTolerance,
28
28
  )
29
29
  from meilisearch_python_sdk.models.task import TaskInfo
30
+ from meilisearch_python_sdk.plugins import (
31
+ AsyncDocumentPlugin,
32
+ AsyncEvent,
33
+ AsyncIndexPlugins,
34
+ AsyncPlugin,
35
+ AsyncPostSearchPlugin,
36
+ DocumentPlugin,
37
+ Event,
38
+ IndexPlugins,
39
+ Plugin,
40
+ PostSearchPlugin,
41
+ )
30
42
  from meilisearch_python_sdk.types import Filter, JsonDict, JsonMapping
31
43
 
32
44
 
33
- class BaseIndex:
45
+ class _BaseIndex:
34
46
  def __init__(
35
47
  self,
36
48
  uid: str,
@@ -62,7 +74,7 @@ class BaseIndex:
62
74
  self.updated_at = iso_to_date_time(updated_at_iso_str)
63
75
 
64
76
 
65
- class AsyncIndex(BaseIndex):
77
+ class AsyncIndex(_BaseIndex):
66
78
  """AsyncIndex class gives access to all indexes routes and child routes.
67
79
 
68
80
  https://docs.meilisearch.com/reference/api/indexes.html
@@ -75,6 +87,7 @@ class AsyncIndex(BaseIndex):
75
87
  primary_key: str | None = None,
76
88
  created_at: str | datetime | None = None,
77
89
  updated_at: str | datetime | None = None,
90
+ plugins: AsyncIndexPlugins | None = None,
78
91
  ):
79
92
  """Class initializer.
80
93
 
@@ -86,10 +99,374 @@ class AsyncIndex(BaseIndex):
86
99
  primary_key: The primary key of the documents. Defaults to None.
87
100
  created_at: The date and time the index was created. Defaults to None.
88
101
  updated_at: The date and time the index was last updated. Defaults to None.
102
+ plugins: Optional plugins can be provided to extend functionality.
89
103
  """
90
104
  super().__init__(uid, primary_key, created_at, updated_at)
91
105
  self.http_client = http_client
92
106
  self._http_requests = AsyncHttpRequests(http_client)
107
+ self.plugins = plugins
108
+
109
+ @cached_property
110
+ def _concurrent_add_documents_plugins(self) -> list[AsyncPlugin | AsyncDocumentPlugin] | None:
111
+ if not self.plugins or not self.plugins.add_documents_plugins:
112
+ return None
113
+
114
+ plugins = []
115
+ for plugin in self.plugins.add_documents_plugins:
116
+ if plugin.CONCURRENT_EVENT:
117
+ plugins.append(plugin)
118
+
119
+ if not plugins:
120
+ return None
121
+
122
+ return plugins
123
+
124
+ @cached_property
125
+ def _post_add_documents_plugins(self) -> list[AsyncPlugin | AsyncDocumentPlugin] | None:
126
+ if not self.plugins or not self.plugins.add_documents_plugins:
127
+ return None
128
+
129
+ plugins = []
130
+ for plugin in self.plugins.add_documents_plugins:
131
+ if plugin.POST_EVENT:
132
+ plugins.append(plugin)
133
+
134
+ if not plugins:
135
+ return None
136
+
137
+ return plugins
138
+
139
+ @cached_property
140
+ def _pre_add_documents_plugins(self) -> list[AsyncPlugin | AsyncDocumentPlugin] | None:
141
+ if not self.plugins or not self.plugins.add_documents_plugins:
142
+ return None
143
+
144
+ plugins = []
145
+ for plugin in self.plugins.add_documents_plugins:
146
+ if plugin.PRE_EVENT:
147
+ plugins.append(plugin)
148
+
149
+ if not plugins:
150
+ return None
151
+
152
+ return plugins
153
+
154
+ @cached_property
155
+ def _concurrent_delete_all_documents_plugins(self) -> list[AsyncPlugin] | None:
156
+ if not self.plugins or not self.plugins.delete_all_documents_plugins:
157
+ return None
158
+
159
+ plugins = []
160
+ for plugin in self.plugins.delete_all_documents_plugins:
161
+ if plugin.CONCURRENT_EVENT:
162
+ plugins.append(plugin)
163
+
164
+ if not plugins:
165
+ return None
166
+
167
+ return plugins
168
+
169
+ @cached_property
170
+ def _post_delete_all_documents_plugins(self) -> list[AsyncPlugin] | None:
171
+ if not self.plugins or not self.plugins.delete_all_documents_plugins:
172
+ return None
173
+
174
+ plugins = []
175
+ for plugin in self.plugins.delete_all_documents_plugins:
176
+ if plugin.POST_EVENT:
177
+ plugins.append(plugin)
178
+
179
+ if not plugins:
180
+ return None
181
+
182
+ return plugins
183
+
184
+ @cached_property
185
+ def _pre_delete_all_documents_plugins(self) -> list[AsyncPlugin] | None:
186
+ if not self.plugins or not self.plugins.delete_all_documents_plugins:
187
+ return None
188
+
189
+ plugins = []
190
+ for plugin in self.plugins.delete_all_documents_plugins:
191
+ if plugin.PRE_EVENT:
192
+ plugins.append(plugin)
193
+
194
+ if not plugins:
195
+ return None
196
+
197
+ return plugins
198
+
199
+ @cached_property
200
+ def _concurrent_delete_document_plugins(self) -> list[AsyncPlugin] | None:
201
+ if not self.plugins or not self.plugins.delete_document_plugins:
202
+ return None
203
+
204
+ plugins = []
205
+ for plugin in self.plugins.delete_document_plugins:
206
+ if plugin.CONCURRENT_EVENT:
207
+ plugins.append(plugin)
208
+
209
+ if not plugins:
210
+ return None
211
+
212
+ return plugins
213
+
214
+ @cached_property
215
+ def _post_delete_document_plugins(self) -> list[AsyncPlugin] | None:
216
+ if not self.plugins or not self.plugins.delete_document_plugins:
217
+ return None
218
+
219
+ plugins = []
220
+ for plugin in self.plugins.delete_document_plugins:
221
+ if plugin.POST_EVENT:
222
+ plugins.append(plugin)
223
+
224
+ if not plugins:
225
+ return None
226
+
227
+ return plugins
228
+
229
+ @cached_property
230
+ def _pre_delete_document_plugins(self) -> list[AsyncPlugin] | None:
231
+ if not self.plugins or not self.plugins.delete_document_plugins:
232
+ return None
233
+
234
+ plugins = []
235
+ for plugin in self.plugins.delete_document_plugins:
236
+ if plugin.PRE_EVENT:
237
+ plugins.append(plugin)
238
+
239
+ if not plugins:
240
+ return None
241
+
242
+ return plugins
243
+
244
+ @cached_property
245
+ def _concurrent_delete_documents_plugins(self) -> list[AsyncPlugin] | None:
246
+ if not self.plugins or not self.plugins.delete_documents_plugins:
247
+ return None
248
+
249
+ plugins = []
250
+ for plugin in self.plugins.delete_documents_plugins:
251
+ if plugin.CONCURRENT_EVENT:
252
+ plugins.append(plugin)
253
+
254
+ if not plugins:
255
+ return None
256
+
257
+ return plugins
258
+
259
+ @cached_property
260
+ def _post_delete_documents_plugins(self) -> list[AsyncPlugin] | None:
261
+ if not self.plugins or not self.plugins.delete_documents_plugins:
262
+ return None
263
+
264
+ plugins = []
265
+ for plugin in self.plugins.delete_documents_plugins:
266
+ if plugin.POST_EVENT:
267
+ plugins.append(plugin)
268
+
269
+ if not plugins:
270
+ return None
271
+
272
+ return plugins
273
+
274
+ @cached_property
275
+ def _pre_delete_documents_plugins(self) -> list[AsyncPlugin] | None:
276
+ if not self.plugins or not self.plugins.delete_documents_plugins:
277
+ return None
278
+
279
+ plugins = []
280
+ for plugin in self.plugins.delete_documents_plugins:
281
+ if plugin.PRE_EVENT:
282
+ plugins.append(plugin)
283
+
284
+ if not plugins:
285
+ return None
286
+
287
+ return plugins
288
+
289
+ @cached_property
290
+ def _concurrent_delete_documents_by_filter_plugins(self) -> list[AsyncPlugin] | None:
291
+ if not self.plugins or not self.plugins.delete_documents_by_filter_plugins:
292
+ return None
293
+
294
+ plugins = []
295
+ for plugin in self.plugins.delete_documents_by_filter_plugins:
296
+ if plugin.CONCURRENT_EVENT:
297
+ plugins.append(plugin)
298
+
299
+ if not plugins:
300
+ return None
301
+
302
+ return plugins
303
+
304
+ @cached_property
305
+ def _post_delete_documents_by_filter_plugins(self) -> list[AsyncPlugin] | None:
306
+ if not self.plugins or not self.plugins.delete_documents_by_filter_plugins:
307
+ return None
308
+
309
+ plugins = []
310
+ for plugin in self.plugins.delete_documents_by_filter_plugins:
311
+ if plugin.POST_EVENT:
312
+ plugins.append(plugin)
313
+
314
+ if not plugins:
315
+ return None
316
+
317
+ return plugins
318
+
319
+ @cached_property
320
+ def _pre_delete_documents_by_filter_plugins(self) -> list[AsyncPlugin] | None:
321
+ if not self.plugins or not self.plugins.delete_documents_by_filter_plugins:
322
+ return None
323
+
324
+ plugins = []
325
+ for plugin in self.plugins.delete_documents_by_filter_plugins:
326
+ if plugin.PRE_EVENT:
327
+ plugins.append(plugin)
328
+
329
+ if not plugins:
330
+ return None
331
+
332
+ return plugins
333
+
334
+ @cached_property
335
+ def _concurrent_facet_search_plugins(self) -> list[AsyncPlugin] | None:
336
+ if not self.plugins or not self.plugins.facet_search_plugins:
337
+ return None
338
+
339
+ plugins = []
340
+ for plugin in self.plugins.facet_search_plugins:
341
+ if plugin.CONCURRENT_EVENT:
342
+ plugins.append(plugin)
343
+
344
+ if not plugins:
345
+ return None
346
+
347
+ return plugins
348
+
349
+ @cached_property
350
+ def _post_facet_search_plugins(self) -> list[AsyncPlugin] | None:
351
+ if not self.plugins or not self.plugins.facet_search_plugins:
352
+ return None
353
+
354
+ plugins = []
355
+ for plugin in self.plugins.facet_search_plugins:
356
+ if plugin.POST_EVENT:
357
+ plugins.append(plugin)
358
+
359
+ if not plugins:
360
+ return None
361
+
362
+ return plugins
363
+
364
+ @cached_property
365
+ def _pre_facet_search_plugins(self) -> list[AsyncPlugin] | None:
366
+ if not self.plugins or not self.plugins.facet_search_plugins:
367
+ return None
368
+
369
+ plugins = []
370
+ for plugin in self.plugins.facet_search_plugins:
371
+ if plugin.PRE_EVENT:
372
+ plugins.append(plugin)
373
+
374
+ if not plugins:
375
+ return None
376
+
377
+ return plugins
378
+
379
+ @cached_property
380
+ def _concurrent_search_plugins(self) -> list[AsyncPlugin | AsyncPostSearchPlugin] | None:
381
+ if not self.plugins or not self.plugins.search_plugins:
382
+ return None
383
+
384
+ plugins = []
385
+ for plugin in self.plugins.search_plugins:
386
+ if plugin.CONCURRENT_EVENT:
387
+ plugins.append(plugin)
388
+
389
+ if not plugins:
390
+ return None
391
+
392
+ return plugins
393
+
394
+ @cached_property
395
+ def _post_search_plugins(self) -> list[AsyncPlugin | AsyncPostSearchPlugin] | None:
396
+ if not self.plugins or not self.plugins.search_plugins:
397
+ return None
398
+
399
+ plugins = []
400
+ for plugin in self.plugins.search_plugins:
401
+ if plugin.POST_EVENT:
402
+ plugins.append(plugin)
403
+
404
+ if not plugins:
405
+ return None
406
+
407
+ return plugins
408
+
409
+ @cached_property
410
+ def _pre_search_plugins(self) -> list[AsyncPlugin | AsyncPostSearchPlugin] | None:
411
+ if not self.plugins or not self.plugins.search_plugins:
412
+ return None
413
+
414
+ plugins = []
415
+ for plugin in self.plugins.search_plugins:
416
+ if plugin.PRE_EVENT:
417
+ plugins.append(plugin)
418
+
419
+ if not plugins:
420
+ return None
421
+
422
+ return plugins
423
+
424
+ @cached_property
425
+ def _concurrent_update_documents_plugins(
426
+ self,
427
+ ) -> list[AsyncPlugin | AsyncDocumentPlugin] | None:
428
+ if not self.plugins or not self.plugins.update_documents_plugins:
429
+ return None
430
+
431
+ plugins = []
432
+ for plugin in self.plugins.update_documents_plugins:
433
+ if plugin.CONCURRENT_EVENT:
434
+ plugins.append(plugin)
435
+
436
+ if not plugins:
437
+ return None
438
+
439
+ return plugins
440
+
441
+ @cached_property
442
+ def _post_update_documents_plugins(self) -> list[AsyncPlugin | AsyncDocumentPlugin] | None:
443
+ if not self.plugins or not self.plugins.update_documents_plugins:
444
+ return None
445
+
446
+ plugins = []
447
+ for plugin in self.plugins.update_documents_plugins:
448
+ if plugin.POST_EVENT:
449
+ plugins.append(plugin)
450
+
451
+ if not plugins:
452
+ return None
453
+
454
+ return plugins
455
+
456
+ @cached_property
457
+ def _pre_update_documents_plugins(self) -> list[AsyncPlugin | AsyncDocumentPlugin] | None:
458
+ if not self.plugins or not self.plugins.update_documents_plugins:
459
+ return None
460
+
461
+ plugins = []
462
+ for plugin in self.plugins.update_documents_plugins:
463
+ if plugin.PRE_EVENT:
464
+ plugins.append(plugin)
465
+
466
+ if not plugins:
467
+ return None
468
+
469
+ return plugins
93
470
 
94
471
  async def delete(self) -> TaskInfo:
95
472
  """Deletes the index.
@@ -229,6 +606,7 @@ class AsyncIndex(BaseIndex):
229
606
  primary_key: str | None = None,
230
607
  *,
231
608
  settings: MeilisearchSettings | None = None,
609
+ plugins: AsyncIndexPlugins | None = None,
232
610
  ) -> AsyncIndex:
233
611
  """Creates a new index.
234
612
 
@@ -246,6 +624,7 @@ class AsyncIndex(BaseIndex):
246
624
  adding documents will cause the documents to be re-indexed. Because of this it will be
247
625
  faster to update them before adding documents. Defaults to None (i.e. default
248
626
  Meilisearch index settings).
627
+ plugins: Optional plugins can be provided to extend functionality.
249
628
 
250
629
  Returns:
251
630
 
@@ -280,6 +659,7 @@ class AsyncIndex(BaseIndex):
280
659
  primary_key=index_dict["primaryKey"],
281
660
  created_at=index_dict["createdAt"],
282
661
  updated_at=index_dict["updatedAt"],
662
+ plugins=plugins,
283
663
  )
284
664
 
285
665
  if settings:
@@ -396,6 +776,7 @@ class AsyncIndex(BaseIndex):
396
776
  >>> index = client.index("movies")
397
777
  >>> search_results = await index.search("Tron")
398
778
  """
779
+
399
780
  body = _process_search_parameters(
400
781
  q=query,
401
782
  offset=offset,
@@ -419,10 +800,134 @@ class AsyncIndex(BaseIndex):
419
800
  show_ranking_score_details=show_ranking_score_details,
420
801
  vector=vector,
421
802
  )
803
+ search_url = f"{self._base_url_with_uid}/search"
804
+
805
+ if self._pre_search_plugins:
806
+ await AsyncIndex._run_plugins(
807
+ self._pre_search_plugins,
808
+ AsyncEvent.PRE,
809
+ query=query,
810
+ offset=offset,
811
+ limit=limit,
812
+ filter=filter,
813
+ facets=facets,
814
+ attributes_to_retrieve=attributes_to_retrieve,
815
+ attributes_to_crop=attributes_to_crop,
816
+ crop_length=crop_length,
817
+ attributes_to_highlight=attributes_to_highlight,
818
+ sort=sort,
819
+ show_matches_position=show_matches_position,
820
+ highlight_pre_tag=highlight_pre_tag,
821
+ highlight_post_tag=highlight_post_tag,
822
+ crop_marker=crop_marker,
823
+ matching_strategy=matching_strategy,
824
+ hits_per_page=hits_per_page,
825
+ page=page,
826
+ attributes_to_search_on=attributes_to_search_on,
827
+ show_ranking_score=show_ranking_score,
828
+ show_ranking_score_details=show_ranking_score_details,
829
+ vector=vector,
830
+ )
422
831
 
423
- response = await self._http_requests.post(f"{self._base_url_with_uid}/search", body=body)
832
+ if self._concurrent_search_plugins:
833
+ if not use_task_groups():
834
+ concurrent_tasks: Any = []
835
+ for plugin in self._concurrent_search_plugins:
836
+ if _plugin_has_method(plugin, "run_plugin"):
837
+ concurrent_tasks.append(
838
+ plugin.run_plugin( # type: ignore[union-attr]
839
+ event=AsyncEvent.CONCURRENT,
840
+ query=query,
841
+ offset=offset,
842
+ limit=limit,
843
+ filter=filter,
844
+ facets=facets,
845
+ attributes_to_retrieve=attributes_to_retrieve,
846
+ attributes_to_crop=attributes_to_crop,
847
+ crop_length=crop_length,
848
+ attributes_to_highlight=attributes_to_highlight,
849
+ sort=sort,
850
+ show_matches_position=show_matches_position,
851
+ highlight_pre_tag=highlight_pre_tag,
852
+ highlight_post_tag=highlight_post_tag,
853
+ crop_marker=crop_marker,
854
+ matching_strategy=matching_strategy,
855
+ hits_per_page=hits_per_page,
856
+ page=page,
857
+ attributes_to_search_on=attributes_to_search_on,
858
+ show_ranking_score=show_ranking_score,
859
+ show_ranking_score_details=show_ranking_score_details,
860
+ vector=vector,
861
+ )
862
+ )
863
+
864
+ concurrent_tasks.append(self._http_requests.post(search_url, body=body))
865
+
866
+ responses = await asyncio.gather(*concurrent_tasks)
867
+ result = SearchResults(**responses[-1].json())
868
+ if self._post_search_plugins:
869
+ post = await AsyncIndex._run_plugins(
870
+ self._post_search_plugins, AsyncEvent.POST, search_results=result
871
+ )
872
+ if post.get("search_result"):
873
+ result = post["search_result"]
874
+
875
+ return result
876
+
877
+ async with asyncio.TaskGroup() as tg: # type: ignore[attr-defined]
878
+ for plugin in self._concurrent_search_plugins:
879
+ if _plugin_has_method(plugin, "run_plugin"):
880
+ tg.create_task(
881
+ plugin.run_plugin( # type: ignore[union-attr]
882
+ event=AsyncEvent.CONCURRENT,
883
+ query=query,
884
+ offset=offset,
885
+ limit=limit,
886
+ filter=filter,
887
+ facets=facets,
888
+ attributes_to_retrieve=attributes_to_retrieve,
889
+ attributes_to_crop=attributes_to_crop,
890
+ crop_length=crop_length,
891
+ attributes_to_highlight=attributes_to_highlight,
892
+ sort=sort,
893
+ show_matches_position=show_matches_position,
894
+ highlight_pre_tag=highlight_pre_tag,
895
+ highlight_post_tag=highlight_post_tag,
896
+ crop_marker=crop_marker,
897
+ matching_strategy=matching_strategy,
898
+ hits_per_page=hits_per_page,
899
+ page=page,
900
+ attributes_to_search_on=attributes_to_search_on,
901
+ show_ranking_score=show_ranking_score,
902
+ show_ranking_score_details=show_ranking_score_details,
903
+ vector=vector,
904
+ )
905
+ )
424
906
 
425
- return SearchResults(**response.json())
907
+ response_coroutine = tg.create_task(self._http_requests.post(search_url, body=body))
908
+
909
+ response = await response_coroutine
910
+ result = SearchResults(**response.json())
911
+ if self._post_search_plugins:
912
+ post = await AsyncIndex._run_plugins(
913
+ self._post_search_plugins, AsyncEvent.POST, search_results=result
914
+ )
915
+ if post.get("search_result"):
916
+ result = post["search_result"]
917
+
918
+ return result
919
+
920
+ response = await self._http_requests.post(search_url, body=body)
921
+ result = SearchResults(**response.json())
922
+
923
+ if self._post_search_plugins:
924
+ post = await AsyncIndex._run_plugins(
925
+ self._post_search_plugins, AsyncEvent.POST, search_results=result
926
+ )
927
+ if post.get("search_result"):
928
+ result = post["search_result"]
929
+
930
+ return result
426
931
 
427
932
  async def facet_search(
428
933
  self,
@@ -542,12 +1047,132 @@ class AsyncIndex(BaseIndex):
542
1047
  show_ranking_score_details=show_ranking_score_details,
543
1048
  vector=vector,
544
1049
  )
1050
+ search_url = f"{self._base_url_with_uid}/facet-search"
1051
+
1052
+ if self._pre_facet_search_plugins:
1053
+ await AsyncIndex._run_plugins(
1054
+ self._pre_facet_search_plugins,
1055
+ AsyncEvent.PRE,
1056
+ query=query,
1057
+ offset=offset,
1058
+ limit=limit,
1059
+ filter=filter,
1060
+ facets=facets,
1061
+ attributes_to_retrieve=attributes_to_retrieve,
1062
+ attributes_to_crop=attributes_to_crop,
1063
+ crop_length=crop_length,
1064
+ attributes_to_highlight=attributes_to_highlight,
1065
+ sort=sort,
1066
+ show_matches_position=show_matches_position,
1067
+ highlight_pre_tag=highlight_pre_tag,
1068
+ highlight_post_tag=highlight_post_tag,
1069
+ crop_marker=crop_marker,
1070
+ matching_strategy=matching_strategy,
1071
+ hits_per_page=hits_per_page,
1072
+ page=page,
1073
+ attributes_to_search_on=attributes_to_search_on,
1074
+ show_ranking_score=show_ranking_score,
1075
+ show_ranking_score_details=show_ranking_score_details,
1076
+ vector=vector,
1077
+ )
545
1078
 
546
- response = await self._http_requests.post(
547
- f"{self._base_url_with_uid}/facet-search", body=body
548
- )
1079
+ if self._concurrent_facet_search_plugins:
1080
+ if not use_task_groups():
1081
+ tasks: Any = []
1082
+ for plugin in self._concurrent_facet_search_plugins:
1083
+ if _plugin_has_method(plugin, "run_plugin"):
1084
+ tasks.append(
1085
+ plugin.run_plugin( # type: ignore[union-attr]
1086
+ event=AsyncEvent.CONCURRENT,
1087
+ query=query,
1088
+ offset=offset,
1089
+ limit=limit,
1090
+ filter=filter,
1091
+ facets=facets,
1092
+ attributes_to_retrieve=attributes_to_retrieve,
1093
+ attributes_to_crop=attributes_to_crop,
1094
+ crop_length=crop_length,
1095
+ attributes_to_highlight=attributes_to_highlight,
1096
+ sort=sort,
1097
+ show_matches_position=show_matches_position,
1098
+ highlight_pre_tag=highlight_pre_tag,
1099
+ highlight_post_tag=highlight_post_tag,
1100
+ crop_marker=crop_marker,
1101
+ matching_strategy=matching_strategy,
1102
+ hits_per_page=hits_per_page,
1103
+ page=page,
1104
+ attributes_to_search_on=attributes_to_search_on,
1105
+ show_ranking_score=show_ranking_score,
1106
+ show_ranking_score_details=show_ranking_score_details,
1107
+ vector=vector,
1108
+ )
1109
+ )
1110
+
1111
+ tasks.append(self._http_requests.post(search_url, body=body))
1112
+ responses = await asyncio.gather(*tasks)
1113
+ result = FacetSearchResults(**responses[-1].json())
1114
+ if self._post_facet_search_plugins:
1115
+ post = await AsyncIndex._run_plugins(
1116
+ self._post_facet_search_plugins, AsyncEvent.POST, result=result
1117
+ )
1118
+ if isinstance(post["generic_result"], FacetSearchResults):
1119
+ result = post["generic_result"]
1120
+
1121
+ return result
1122
+
1123
+ async with asyncio.TaskGroup() as tg: # type: ignore[attr-defined]
1124
+ for plugin in self._concurrent_facet_search_plugins:
1125
+ if _plugin_has_method(plugin, "run_plugin"):
1126
+ tg.create_task(
1127
+ plugin.run_plugin( # type: ignore[union-attr]
1128
+ event=AsyncEvent.CONCURRENT,
1129
+ query=query,
1130
+ offset=offset,
1131
+ limit=limit,
1132
+ filter=filter,
1133
+ facets=facets,
1134
+ attributes_to_retrieve=attributes_to_retrieve,
1135
+ attributes_to_crop=attributes_to_crop,
1136
+ crop_length=crop_length,
1137
+ attributes_to_highlight=attributes_to_highlight,
1138
+ sort=sort,
1139
+ show_matches_position=show_matches_position,
1140
+ highlight_pre_tag=highlight_pre_tag,
1141
+ highlight_post_tag=highlight_post_tag,
1142
+ crop_marker=crop_marker,
1143
+ matching_strategy=matching_strategy,
1144
+ hits_per_page=hits_per_page,
1145
+ page=page,
1146
+ attributes_to_search_on=attributes_to_search_on,
1147
+ show_ranking_score=show_ranking_score,
1148
+ show_ranking_score_details=show_ranking_score_details,
1149
+ vector=vector,
1150
+ )
1151
+ )
549
1152
 
550
- return FacetSearchResults(**response.json())
1153
+ response_coroutine = tg.create_task(self._http_requests.post(search_url, body=body))
1154
+
1155
+ response = await response_coroutine
1156
+ result = FacetSearchResults(**response.json())
1157
+ if self._post_facet_search_plugins:
1158
+ post = await AsyncIndex._run_plugins(
1159
+ self._post_facet_search_plugins, AsyncEvent.POST, result=result
1160
+ )
1161
+ if isinstance(post["generic_result"], FacetSearchResults):
1162
+ result = post["generic_result"]
1163
+
1164
+ return result
1165
+
1166
+ response = await self._http_requests.post(search_url, body=body)
1167
+ result = FacetSearchResults(**response.json())
1168
+ if self._post_facet_search_plugins:
1169
+ post = await AsyncIndex._run_plugins(
1170
+ self._post_facet_search_plugins, AsyncEvent.POST, result=result
1171
+ )
1172
+ if isinstance(post["generic_result"], FacetSearchResults):
1173
+ result = post["generic_result"]
1174
+
1175
+ return result
551
1176
 
552
1177
  async def get_document(self, document_id: str) -> JsonDict:
553
1178
  """Get one document with given document identifier.
@@ -671,9 +1296,103 @@ class AsyncIndex(BaseIndex):
671
1296
  else:
672
1297
  url = self._documents_url
673
1298
 
1299
+ if self._pre_add_documents_plugins:
1300
+ pre = await AsyncIndex._run_plugins(
1301
+ self._pre_add_documents_plugins,
1302
+ AsyncEvent.PRE,
1303
+ documents=documents,
1304
+ primary_key=primary_key,
1305
+ )
1306
+ if pre.get("document_result"):
1307
+ documents = pre["document_result"]
1308
+
1309
+ if self._concurrent_add_documents_plugins:
1310
+ if not use_task_groups():
1311
+ tasks: Any = []
1312
+ for plugin in self._concurrent_add_documents_plugins:
1313
+ if _plugin_has_method(plugin, "run_plugin"):
1314
+ tasks.append(
1315
+ plugin.run_plugin( # type: ignore[union-attr]
1316
+ event=AsyncEvent.CONCURRENT,
1317
+ documents=documents,
1318
+ primary_key=primary_key,
1319
+ )
1320
+ )
1321
+ if _plugin_has_method(plugin, "run_document_plugin"):
1322
+ tasks.append(
1323
+ plugin.run_document_plugin( # type: ignore[union-attr]
1324
+ event=AsyncEvent.CONCURRENT,
1325
+ documents=documents,
1326
+ primary_key=primary_key,
1327
+ )
1328
+ )
1329
+
1330
+ tasks.append(self._http_requests.post(url, documents))
1331
+
1332
+ responses = await asyncio.gather(*tasks)
1333
+ result = TaskInfo(**responses[-1].json())
1334
+ if self._post_add_documents_plugins:
1335
+ post = await AsyncIndex._run_plugins(
1336
+ self._post_add_documents_plugins,
1337
+ AsyncEvent.POST,
1338
+ result=result,
1339
+ documents=documents,
1340
+ primary_key=primary_key,
1341
+ )
1342
+ if isinstance(post["generic_result"], TaskInfo):
1343
+ result = post["generic_result"]
1344
+ return result
1345
+
1346
+ async with asyncio.TaskGroup() as tg: # type: ignore[attr-defined]
1347
+ for plugin in self._concurrent_add_documents_plugins:
1348
+ if _plugin_has_method(plugin, "run_plugin"):
1349
+ tg.create_task(
1350
+ plugin.run_plugin( # type: ignore[union-attr]
1351
+ event=AsyncEvent.CONCURRENT,
1352
+ documents=documents,
1353
+ primary_key=primary_key,
1354
+ )
1355
+ )
1356
+ if _plugin_has_method(plugin, "run_document_plugin"):
1357
+ tg.create_task(
1358
+ plugin.run_document_plugin( # type: ignore[union-attr]
1359
+ event=AsyncEvent.CONCURRENT,
1360
+ documents=documents,
1361
+ primary_key=primary_key,
1362
+ )
1363
+ )
1364
+
1365
+ response_coroutine = tg.create_task(self._http_requests.post(url, documents))
1366
+
1367
+ response = await response_coroutine
1368
+ result = TaskInfo(**response.json())
1369
+ if self._post_add_documents_plugins:
1370
+ post = await AsyncIndex._run_plugins(
1371
+ self._post_add_documents_plugins,
1372
+ AsyncEvent.POST,
1373
+ result=result,
1374
+ documents=documents,
1375
+ primary_key=primary_key,
1376
+ )
1377
+ if isinstance(post["generic_result"], TaskInfo):
1378
+ result = post["generic_result"]
1379
+
1380
+ return result
1381
+
674
1382
  response = await self._http_requests.post(url, documents)
1383
+ result = TaskInfo(**response.json())
1384
+ if self._post_add_documents_plugins:
1385
+ post = await AsyncIndex._run_plugins(
1386
+ self._post_add_documents_plugins,
1387
+ AsyncEvent.POST,
1388
+ result=result,
1389
+ documents=documents,
1390
+ primary_key=primary_key,
1391
+ )
1392
+ if isinstance(post["generic_result"], TaskInfo):
1393
+ result = post["generic_result"]
675
1394
 
676
- return TaskInfo(**response.json())
1395
+ return result
677
1396
 
678
1397
  async def add_documents_in_batches(
679
1398
  self,
@@ -1112,9 +1831,105 @@ class AsyncIndex(BaseIndex):
1112
1831
  else:
1113
1832
  url = self._documents_url
1114
1833
 
1834
+ if self._pre_update_documents_plugins:
1835
+ pre = await AsyncIndex._run_plugins(
1836
+ self._pre_update_documents_plugins,
1837
+ AsyncEvent.PRE,
1838
+ documents=documents,
1839
+ primary_key=primary_key,
1840
+ )
1841
+ if pre.get("document_result"):
1842
+ documents = pre["document_result"]
1843
+
1844
+ if self._concurrent_update_documents_plugins:
1845
+ if not use_task_groups():
1846
+ tasks: Any = []
1847
+ for plugin in self._concurrent_update_documents_plugins:
1848
+ if _plugin_has_method(plugin, "run_plugin"):
1849
+ tasks.append(
1850
+ plugin.run_plugin( # type: ignore[union-attr]
1851
+ event=AsyncEvent.CONCURRENT,
1852
+ documents=documents,
1853
+ primary_key=primary_key,
1854
+ )
1855
+ )
1856
+ if _plugin_has_method(plugin, "run_document_plugin"):
1857
+ tasks.append(
1858
+ plugin.run_document_plugin( # type: ignore[union-attr]
1859
+ event=AsyncEvent.CONCURRENT,
1860
+ documents=documents,
1861
+ primary_key=primary_key,
1862
+ )
1863
+ )
1864
+
1865
+ tasks.append(self._http_requests.put(url, documents))
1866
+
1867
+ responses = await asyncio.gather(*tasks)
1868
+ result = TaskInfo(**responses[-1].json())
1869
+ if self._post_update_documents_plugins:
1870
+ post = await AsyncIndex._run_plugins(
1871
+ self._post_update_documents_plugins,
1872
+ AsyncEvent.POST,
1873
+ result=result,
1874
+ documents=documents,
1875
+ primary_key=primary_key,
1876
+ )
1877
+ if isinstance(post["generic_result"], TaskInfo):
1878
+ result = post["generic_result"]
1879
+
1880
+ return result
1881
+
1882
+ async with asyncio.TaskGroup() as tg: # type: ignore[attr-defined]
1883
+ for plugin in self._concurrent_update_documents_plugins:
1884
+ if _plugin_has_method(plugin, "run_plugin"):
1885
+ tg.create_task(
1886
+ plugin.run_plugin( # type: ignore[union-attr]
1887
+ event=AsyncEvent.CONCURRENT,
1888
+ documents=documents,
1889
+ primary_key=primary_key,
1890
+ )
1891
+ )
1892
+ if _plugin_has_method(plugin, "run_document_plugin"):
1893
+ tg.create_task(
1894
+ plugin.run_document_plugin( # type: ignore[union-attr]
1895
+ event=AsyncEvent.CONCURRENT,
1896
+ documents=documents,
1897
+ primary_key=primary_key,
1898
+ )
1899
+ )
1900
+
1901
+ response_coroutine = tg.create_task(self._http_requests.put(url, documents))
1902
+
1903
+ response = await response_coroutine
1904
+ result = TaskInfo(**response.json())
1905
+ if self._post_update_documents_plugins:
1906
+ post = await AsyncIndex._run_plugins(
1907
+ self._post_update_documents_plugins,
1908
+ AsyncEvent.POST,
1909
+ result=result,
1910
+ documents=documents,
1911
+ primary_key=primary_key,
1912
+ )
1913
+
1914
+ if isinstance(post["generic_result"], TaskInfo):
1915
+ result = post["generic_result"]
1916
+
1917
+ return result
1918
+
1115
1919
  response = await self._http_requests.put(url, documents)
1920
+ result = TaskInfo(**response.json())
1921
+ if self._post_update_documents_plugins:
1922
+ post = await AsyncIndex._run_plugins(
1923
+ self._post_update_documents_plugins,
1924
+ AsyncEvent.POST,
1925
+ result=result,
1926
+ documents=documents,
1927
+ primary_key=primary_key,
1928
+ )
1929
+ if isinstance(post["generic_result"], TaskInfo):
1930
+ result = post["generic_result"]
1116
1931
 
1117
- return TaskInfo(**response.json())
1932
+ return result
1118
1933
 
1119
1934
  async def update_documents_in_batches(
1120
1935
  self,
@@ -1553,9 +2368,61 @@ class AsyncIndex(BaseIndex):
1553
2368
  >>> index = client.index("movies")
1554
2369
  >>> await index.delete_document("1234")
1555
2370
  """
1556
- response = await self._http_requests.delete(f"{self._documents_url}/{document_id}")
2371
+ url = f"{self._documents_url}/{document_id}"
1557
2372
 
1558
- return TaskInfo(**response.json())
2373
+ if self._pre_delete_document_plugins:
2374
+ await AsyncIndex._run_plugins(
2375
+ self._pre_delete_document_plugins, AsyncEvent.PRE, document_id=document_id
2376
+ )
2377
+
2378
+ if self._concurrent_delete_document_plugins:
2379
+ if not use_task_groups():
2380
+ tasks: Any = []
2381
+ for plugin in self._concurrent_delete_document_plugins:
2382
+ tasks.append(
2383
+ plugin.run_plugin(event=AsyncEvent.CONCURRENT, document_id=document_id)
2384
+ )
2385
+
2386
+ tasks.append(self._http_requests.delete(url))
2387
+
2388
+ responses = await asyncio.gather(*tasks)
2389
+ result = TaskInfo(**responses[-1].json())
2390
+ if self._post_delete_document_plugins:
2391
+ post = await AsyncIndex._run_plugins(
2392
+ self._post_delete_document_plugins, AsyncEvent.POST, result=result
2393
+ )
2394
+ if isinstance(post.get("generic_result"), TaskInfo):
2395
+ result = post["generic_result"]
2396
+ return result
2397
+
2398
+ async with asyncio.TaskGroup() as tg: # type: ignore[attr-defined]
2399
+ for plugin in self._concurrent_delete_document_plugins:
2400
+ tg.create_task(
2401
+ plugin.run_plugin(event=AsyncEvent.CONCURRENT, document_id=document_id)
2402
+ )
2403
+
2404
+ response_coroutine = tg.create_task(self._http_requests.delete(url))
2405
+
2406
+ response = await response_coroutine
2407
+ result = TaskInfo(**response.json())
2408
+ if self._post_delete_document_plugins:
2409
+ post = await AsyncIndex._run_plugins(
2410
+ self._post_delete_document_plugins, event=AsyncEvent.POST, result=result
2411
+ )
2412
+ if isinstance(post["generic_result"], TaskInfo):
2413
+ result = post["generic_result"]
2414
+ return result
2415
+
2416
+ response = await self._http_requests.delete(url)
2417
+ result = TaskInfo(**response.json())
2418
+ if self._post_delete_document_plugins:
2419
+ post = await AsyncIndex._run_plugins(
2420
+ self._post_delete_document_plugins, AsyncEvent.POST, result=result
2421
+ )
2422
+ if isinstance(post["generic_result"], TaskInfo):
2423
+ result = post["generic_result"]
2424
+
2425
+ return result
1559
2426
 
1560
2427
  async def delete_documents(self, ids: list[str]) -> TaskInfo:
1561
2428
  """Delete multiple documents from the index.
@@ -1580,9 +2447,57 @@ class AsyncIndex(BaseIndex):
1580
2447
  >>> index = client.index("movies")
1581
2448
  >>> await index.delete_documents(["1234", "5678"])
1582
2449
  """
1583
- response = await self._http_requests.post(f"{self._documents_url}/delete-batch", ids)
2450
+ url = f"{self._documents_url}/delete-batch"
1584
2451
 
1585
- return TaskInfo(**response.json())
2452
+ if self._pre_delete_documents_plugins:
2453
+ await AsyncIndex._run_plugins(
2454
+ self._pre_delete_documents_plugins, AsyncEvent.PRE, ids=ids
2455
+ )
2456
+
2457
+ if self._concurrent_delete_documents_plugins:
2458
+ if not use_task_groups():
2459
+ tasks: Any = []
2460
+ for plugin in self._concurrent_delete_documents_plugins:
2461
+ tasks.append(plugin.run_plugin(event=AsyncEvent.CONCURRENT, ids=ids))
2462
+
2463
+ tasks.append(self._http_requests.post(url, ids))
2464
+
2465
+ responses = await asyncio.gather(*tasks)
2466
+ result = TaskInfo(**responses[-1].json())
2467
+ if self._post_delete_documents_plugins:
2468
+ post = await AsyncIndex._run_plugins(
2469
+ self._post_delete_documents_plugins, AsyncEvent.POST, result=result
2470
+ )
2471
+ if isinstance(post.get("generic_result"), TaskInfo):
2472
+ result = post["generic_result"]
2473
+ return result
2474
+
2475
+ async with asyncio.TaskGroup() as tg: # type: ignore[attr-defined]
2476
+ for plugin in self._concurrent_delete_documents_plugins:
2477
+ tg.create_task(plugin.run_plugin(event=AsyncEvent.CONCURRENT, ids=ids))
2478
+
2479
+ response_coroutine = tg.create_task(self._http_requests.post(url, ids))
2480
+
2481
+ response = await response_coroutine
2482
+ result = TaskInfo(**response.json())
2483
+ if self._post_delete_documents_plugins:
2484
+ post = await AsyncIndex._run_plugins(
2485
+ self._post_delete_documents_plugins, AsyncEvent.POST, result=result
2486
+ )
2487
+ if isinstance(post["generic_result"], TaskInfo):
2488
+ result = post["generic_result"]
2489
+ return result
2490
+
2491
+ response = await self._http_requests.post(url, ids)
2492
+ result = TaskInfo(**response.json())
2493
+ if self._post_delete_documents_plugins:
2494
+ post = await AsyncIndex._run_plugins(
2495
+ self._post_delete_documents_plugins, AsyncEvent.POST, result=result
2496
+ )
2497
+ if isinstance(post["generic_result"], TaskInfo):
2498
+ result = post["generic_result"]
2499
+
2500
+ return result
1586
2501
 
1587
2502
  async def delete_documents_by_filter(self, filter: Filter) -> TaskInfo:
1588
2503
  """Delete documents from the index by filter.
@@ -1602,16 +2517,66 @@ class AsyncIndex(BaseIndex):
1602
2517
 
1603
2518
  Examples:
1604
2519
 
1605
- >>> from meilisearch_python_sdk import AsyncClient
2520
+ >>> from meilisearch_pyrhon_sdk import AsyncClient
1606
2521
  >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
1607
2522
  >>> index = client.index("movies")
1608
2523
  >>> await index.delete_documents_by_filter("genre=horor"))
1609
2524
  """
1610
- response = await self._http_requests.post(
1611
- f"{self._documents_url}/delete", body={"filter": filter}
1612
- )
2525
+ url = f"{self._documents_url}/delete"
1613
2526
 
1614
- return TaskInfo(**response.json())
2527
+ if self._pre_delete_documents_by_filter_plugins:
2528
+ await AsyncIndex._run_plugins(
2529
+ self._pre_delete_documents_by_filter_plugins, AsyncEvent.PRE, filter=filter
2530
+ )
2531
+
2532
+ if self._concurrent_delete_documents_by_filter_plugins:
2533
+ if not use_task_groups():
2534
+ tasks: Any = []
2535
+ for plugin in self._concurrent_delete_documents_by_filter_plugins:
2536
+ tasks.append(plugin.run_plugin(event=AsyncEvent.CONCURRENT, filter=filter))
2537
+
2538
+ tasks.append(self._http_requests.post(url, body={"filter": filter}))
2539
+
2540
+ responses = await asyncio.gather(*tasks)
2541
+ result = TaskInfo(**responses[-1].json())
2542
+ if self._post_delete_documents_by_filter_plugins:
2543
+ post = await AsyncIndex._run_plugins(
2544
+ self._post_delete_documents_by_filter_plugins,
2545
+ AsyncEvent.POST,
2546
+ result=result,
2547
+ )
2548
+ if isinstance(post["generic_result"], TaskInfo):
2549
+ result = post["generic_result"]
2550
+ return result
2551
+
2552
+ async with asyncio.TaskGroup() as tg: # type: ignore[attr-defined]
2553
+ for plugin in self._concurrent_delete_documents_by_filter_plugins:
2554
+ tg.create_task(plugin.run_plugin(event=AsyncEvent.CONCURRENT, filter=filter))
2555
+
2556
+ response_coroutine = tg.create_task(
2557
+ self._http_requests.post(url, body={"filter": filter})
2558
+ )
2559
+
2560
+ response = await response_coroutine
2561
+ result = TaskInfo(**response.json())
2562
+ if self._post_delete_documents_by_filter_plugins:
2563
+ post = await AsyncIndex._run_plugins(
2564
+ self._post_delete_documents_by_filter_plugins, AsyncEvent.POST, result=result
2565
+ )
2566
+ if isinstance(post["generic_result"], TaskInfo):
2567
+ result = post["generic_result"]
2568
+
2569
+ return result
2570
+
2571
+ response = await self._http_requests.post(url, body={"filter": filter})
2572
+ result = TaskInfo(**response.json())
2573
+ if self._post_delete_documents_by_filter_plugins:
2574
+ post = await AsyncIndex._run_plugins(
2575
+ self._post_delete_documents_by_filter_plugins, AsyncEvent.POST, result=result
2576
+ )
2577
+ if isinstance(post.get("generic_result"), TaskInfo):
2578
+ result = post["generic_result"]
2579
+ return result
1615
2580
 
1616
2581
  async def delete_documents_in_batches_by_filter(
1617
2582
  self, filters: list[str | list[str | list[str]]]
@@ -1673,9 +2638,52 @@ class AsyncIndex(BaseIndex):
1673
2638
  >>> index = client.index("movies")
1674
2639
  >>> await index.delete_all_document()
1675
2640
  """
1676
- response = await self._http_requests.delete(self._documents_url)
2641
+ if self._pre_delete_all_documents_plugins:
2642
+ await AsyncIndex._run_plugins(self._pre_delete_all_documents_plugins, AsyncEvent.PRE)
1677
2643
 
1678
- return TaskInfo(**response.json())
2644
+ if self._concurrent_delete_all_documents_plugins:
2645
+ if not use_task_groups():
2646
+ tasks: Any = []
2647
+ for plugin in self._concurrent_delete_all_documents_plugins:
2648
+ tasks.append(plugin.run_plugin(event=AsyncEvent.CONCURRENT))
2649
+
2650
+ tasks.append(self._http_requests.delete(self._documents_url))
2651
+
2652
+ responses = await asyncio.gather(*tasks)
2653
+ result = TaskInfo(**responses[-1].json())
2654
+ if self._post_delete_all_documents_plugins:
2655
+ post = await AsyncIndex._run_plugins(
2656
+ self._post_delete_all_documents_plugins, AsyncEvent.POST, result=result
2657
+ )
2658
+ if isinstance(post.get("generic_result"), TaskInfo):
2659
+ result = post["generic_result"]
2660
+ return result
2661
+
2662
+ async with asyncio.TaskGroup() as tg: # type: ignore[attr-defined]
2663
+ for plugin in self._concurrent_delete_all_documents_plugins:
2664
+ tg.create_task(plugin.run_plugin(event=AsyncEvent.CONCURRENT))
2665
+
2666
+ response_coroutine = tg.create_task(self._http_requests.delete(self._documents_url))
2667
+
2668
+ response = await response_coroutine
2669
+ result = TaskInfo(**response.json())
2670
+ if self._post_delete_all_documents_plugins:
2671
+ post = await AsyncIndex._run_plugins(
2672
+ self._post_delete_all_documents_plugins, AsyncEvent.POST, result=result
2673
+ )
2674
+ if isinstance(post.get("generic_result"), TaskInfo):
2675
+ result = post["generic_result"]
2676
+ return result
2677
+
2678
+ response = await self._http_requests.delete(self._documents_url)
2679
+ result = TaskInfo(**response.json())
2680
+ if self._post_delete_all_documents_plugins:
2681
+ post = await AsyncIndex._run_plugins(
2682
+ self._post_delete_all_documents_plugins, AsyncEvent.POST, result=result
2683
+ )
2684
+ if isinstance(post.get("generic_result"), TaskInfo):
2685
+ result = post["generic_result"]
2686
+ return result
1679
2687
 
1680
2688
  async def get_settings(self) -> MeilisearchSettings:
1681
2689
  """Get settings of the index.
@@ -2875,8 +3883,76 @@ class AsyncIndex(BaseIndex):
2875
3883
 
2876
3884
  return TaskInfo(**response.json())
2877
3885
 
3886
+ @staticmethod
3887
+ async def _run_plugins(
3888
+ plugins: Sequence[AsyncPlugin | AsyncDocumentPlugin | AsyncPostSearchPlugin],
3889
+ event: AsyncEvent,
3890
+ **kwargs: Any,
3891
+ ) -> dict[str, Any]:
3892
+ generic_plugins = []
3893
+ document_plugins = []
3894
+ search_plugins = []
3895
+ results: dict[str, Any] = {
3896
+ "generic_result": None,
3897
+ "document_result": None,
3898
+ "search_result": None,
3899
+ }
3900
+ if not use_task_groups():
3901
+ for plugin in plugins:
3902
+ if _plugin_has_method(plugin, "run_plugin"):
3903
+ generic_plugins.append(plugin.run_plugin(event=event, **kwargs)) # type: ignore[union-attr]
3904
+ if _plugin_has_method(plugin, "run_document_plugin"):
3905
+ document_plugins.append(plugin.run_document_plugin(event=event, **kwargs)) # type: ignore[union-attr]
3906
+ if _plugin_has_method(plugin, "run_post_search_plugin"):
3907
+ search_plugins.append(plugin.run_post_search_plugin(event=event, **kwargs)) # type: ignore[union-attr]
3908
+ if generic_plugins:
3909
+ generic_results = await asyncio.gather(*generic_plugins)
3910
+ if generic_results:
3911
+ results["generic_result"] = generic_results[-1]
3912
+
3913
+ if document_plugins:
3914
+ document_results = await asyncio.gather(*document_plugins)
3915
+ if document_results:
3916
+ results["document_result"] = document_results[-1]
3917
+ if search_plugins:
3918
+ search_results = await asyncio.gather(*search_plugins)
3919
+ if search_results:
3920
+ results["search_result"] = search_results[-1]
3921
+
3922
+ return results
3923
+
3924
+ async with asyncio.TaskGroup() as tg: # type: ignore[attr-defined]
3925
+ generic_tasks = []
3926
+ document_tasks = []
3927
+ search_tasks = []
3928
+ for plugin in plugins:
3929
+ if _plugin_has_method(plugin, "run_plugin"):
3930
+ generic_tasks.append(tg.create_task(plugin.run_plugin(event=event, **kwargs))) # type: ignore[union-attr]
3931
+ if _plugin_has_method(plugin, "run_document_plugin"):
3932
+ document_tasks.append(
3933
+ tg.create_task(plugin.run_document_plugin(event=event, **kwargs)) # type: ignore[union-attr]
3934
+ )
3935
+ if _plugin_has_method(plugin, "run_post_search_plugin"):
3936
+ search_tasks.append(
3937
+ tg.create_task(plugin.run_post_search_plugin(event=event, **kwargs)) # type: ignore[union-attr]
3938
+ )
3939
+
3940
+ if generic_tasks:
3941
+ for result in reversed(generic_tasks):
3942
+ if result:
3943
+ results["generic_result"] = await result
3944
+ break
2878
3945
 
2879
- class Index(BaseIndex):
3946
+ if document_tasks:
3947
+ results["document_result"] = await document_tasks[-1]
3948
+
3949
+ if search_tasks:
3950
+ results["search_result"] = await search_tasks[-1]
3951
+
3952
+ return results
3953
+
3954
+
3955
+ class Index(_BaseIndex):
2880
3956
  """Index class gives access to all indexes routes and child routes.
2881
3957
 
2882
3958
  https://docs.meilisearch.com/reference/api/indexes.html
@@ -2889,6 +3965,7 @@ class Index(BaseIndex):
2889
3965
  primary_key: str | None = None,
2890
3966
  created_at: str | datetime | None = None,
2891
3967
  updated_at: str | datetime | None = None,
3968
+ plugins: IndexPlugins | None = None,
2892
3969
  ):
2893
3970
  """Class initializer.
2894
3971
 
@@ -2900,10 +3977,252 @@ class Index(BaseIndex):
2900
3977
  primary_key: The primary key of the documents. Defaults to None.
2901
3978
  created_at: The date and time the index was created. Defaults to None.
2902
3979
  updated_at: The date and time the index was last updated. Defaults to None.
3980
+ plugins: Optional plugins can be provided to extend functionality.
2903
3981
  """
2904
3982
  super().__init__(uid, primary_key, created_at, updated_at)
2905
3983
  self.http_client = http_client
2906
3984
  self._http_requests = HttpRequests(http_client)
3985
+ self.plugins = plugins
3986
+
3987
+ @cached_property
3988
+ def _post_add_documents_plugins(self) -> list[Plugin | DocumentPlugin] | None:
3989
+ if not self.plugins or not self.plugins.add_documents_plugins:
3990
+ return None
3991
+
3992
+ plugins = []
3993
+ for plugin in self.plugins.add_documents_plugins:
3994
+ if plugin.POST_EVENT:
3995
+ plugins.append(plugin)
3996
+
3997
+ if not plugins:
3998
+ return None
3999
+
4000
+ return plugins
4001
+
4002
+ @cached_property
4003
+ def _pre_add_documents_plugins(self) -> list[Plugin | DocumentPlugin] | None:
4004
+ if not self.plugins or not self.plugins.add_documents_plugins:
4005
+ return None
4006
+
4007
+ plugins = []
4008
+ for plugin in self.plugins.add_documents_plugins:
4009
+ if plugin.PRE_EVENT:
4010
+ plugins.append(plugin)
4011
+
4012
+ if not plugins:
4013
+ return None
4014
+
4015
+ return plugins
4016
+
4017
+ @cached_property
4018
+ def _post_delete_all_documents_plugins(self) -> list[Plugin] | None:
4019
+ if not self.plugins or not self.plugins.delete_all_documents_plugins:
4020
+ return None
4021
+
4022
+ plugins = []
4023
+ for plugin in self.plugins.delete_all_documents_plugins:
4024
+ if plugin.POST_EVENT:
4025
+ plugins.append(plugin)
4026
+
4027
+ if not plugins:
4028
+ return None
4029
+
4030
+ return plugins
4031
+
4032
+ @cached_property
4033
+ def _pre_delete_all_documents_plugins(self) -> list[Plugin] | None:
4034
+ if not self.plugins or not self.plugins.delete_all_documents_plugins:
4035
+ return None
4036
+
4037
+ plugins = []
4038
+ for plugin in self.plugins.delete_all_documents_plugins:
4039
+ if plugin.PRE_EVENT:
4040
+ plugins.append(plugin)
4041
+
4042
+ if not plugins:
4043
+ return None
4044
+
4045
+ return plugins
4046
+
4047
+ @cached_property
4048
+ def _post_delete_document_plugins(self) -> list[Plugin] | None:
4049
+ if not self.plugins or not self.plugins.delete_document_plugins:
4050
+ return None
4051
+
4052
+ plugins = []
4053
+ for plugin in self.plugins.delete_document_plugins:
4054
+ if plugin.POST_EVENT:
4055
+ plugins.append(plugin)
4056
+
4057
+ if not plugins:
4058
+ return None
4059
+
4060
+ return plugins
4061
+
4062
+ @cached_property
4063
+ def _pre_delete_document_plugins(self) -> list[Plugin] | None:
4064
+ if not self.plugins or not self.plugins.delete_document_plugins:
4065
+ return None
4066
+
4067
+ plugins = []
4068
+ for plugin in self.plugins.delete_document_plugins:
4069
+ if plugin.PRE_EVENT:
4070
+ plugins.append(plugin)
4071
+
4072
+ if not plugins:
4073
+ return None
4074
+
4075
+ return plugins
4076
+
4077
+ @cached_property
4078
+ def _post_delete_documents_plugins(self) -> list[Plugin] | None:
4079
+ if not self.plugins or not self.plugins.delete_documents_plugins:
4080
+ return None
4081
+
4082
+ plugins = []
4083
+ for plugin in self.plugins.delete_documents_plugins:
4084
+ if plugin.POST_EVENT:
4085
+ plugins.append(plugin)
4086
+
4087
+ if not plugins:
4088
+ return None
4089
+
4090
+ return plugins
4091
+
4092
+ @cached_property
4093
+ def _pre_delete_documents_plugins(self) -> list[Plugin] | None:
4094
+ if not self.plugins or not self.plugins.delete_documents_plugins:
4095
+ return None
4096
+
4097
+ plugins = []
4098
+ for plugin in self.plugins.delete_documents_plugins:
4099
+ if plugin.PRE_EVENT:
4100
+ plugins.append(plugin)
4101
+
4102
+ if not plugins:
4103
+ return None
4104
+
4105
+ return plugins
4106
+
4107
+ @cached_property
4108
+ def _post_delete_documents_by_filter_plugins(self) -> list[Plugin] | None:
4109
+ if not self.plugins or not self.plugins.delete_documents_by_filter_plugins:
4110
+ return None
4111
+
4112
+ plugins = []
4113
+ for plugin in self.plugins.delete_documents_by_filter_plugins:
4114
+ if plugin.POST_EVENT:
4115
+ plugins.append(plugin)
4116
+
4117
+ if not plugins:
4118
+ return None
4119
+
4120
+ return plugins
4121
+
4122
+ @cached_property
4123
+ def _pre_delete_documents_by_filter_plugins(self) -> list[Plugin] | None:
4124
+ if not self.plugins or not self.plugins.delete_documents_by_filter_plugins:
4125
+ return None
4126
+
4127
+ plugins = []
4128
+ for plugin in self.plugins.delete_documents_by_filter_plugins:
4129
+ if plugin.PRE_EVENT:
4130
+ plugins.append(plugin)
4131
+
4132
+ if not plugins:
4133
+ return None
4134
+
4135
+ return plugins
4136
+
4137
+ @cached_property
4138
+ def _post_facet_search_plugins(self) -> list[Plugin] | None:
4139
+ if not self.plugins or not self.plugins.facet_search_plugins:
4140
+ return None
4141
+
4142
+ plugins = []
4143
+ for plugin in self.plugins.facet_search_plugins:
4144
+ if plugin.POST_EVENT:
4145
+ plugins.append(plugin)
4146
+
4147
+ if not plugins:
4148
+ return None
4149
+
4150
+ return plugins
4151
+
4152
+ @cached_property
4153
+ def _pre_facet_search_plugins(self) -> list[Plugin] | None:
4154
+ if not self.plugins or not self.plugins.facet_search_plugins:
4155
+ return None
4156
+
4157
+ plugins = []
4158
+ for plugin in self.plugins.facet_search_plugins:
4159
+ if plugin.PRE_EVENT:
4160
+ plugins.append(plugin)
4161
+
4162
+ if not plugins:
4163
+ return None
4164
+
4165
+ return plugins
4166
+
4167
+ @cached_property
4168
+ def _post_search_plugins(self) -> list[Plugin | PostSearchPlugin] | None:
4169
+ if not self.plugins or not self.plugins.search_plugins:
4170
+ return None
4171
+
4172
+ plugins = []
4173
+ for plugin in self.plugins.search_plugins:
4174
+ if plugin.POST_EVENT:
4175
+ plugins.append(plugin)
4176
+
4177
+ if not plugins:
4178
+ return None
4179
+
4180
+ return plugins
4181
+
4182
+ @cached_property
4183
+ def _pre_search_plugins(self) -> list[Plugin | PostSearchPlugin] | None:
4184
+ if not self.plugins or not self.plugins.search_plugins:
4185
+ return None
4186
+
4187
+ plugins = []
4188
+ for plugin in self.plugins.search_plugins:
4189
+ if plugin.PRE_EVENT:
4190
+ plugins.append(plugin)
4191
+
4192
+ if not plugins:
4193
+ return None
4194
+
4195
+ return plugins
4196
+
4197
+ @cached_property
4198
+ def _post_update_documents_plugins(self) -> list[Plugin | DocumentPlugin] | None:
4199
+ if not self.plugins or not self.plugins.update_documents_plugins:
4200
+ return None
4201
+
4202
+ plugins = []
4203
+ for plugin in self.plugins.update_documents_plugins:
4204
+ if plugin.POST_EVENT:
4205
+ plugins.append(plugin)
4206
+
4207
+ if not plugins:
4208
+ return None
4209
+
4210
+ return plugins
4211
+
4212
+ @cached_property
4213
+ def _pre_update_documents_plugins(self) -> list[Plugin | DocumentPlugin] | None:
4214
+ if not self.plugins or not self.plugins.update_documents_plugins:
4215
+ return None
4216
+
4217
+ plugins = []
4218
+ for plugin in self.plugins.update_documents_plugins:
4219
+ if plugin.PRE_EVENT:
4220
+ plugins.append(plugin)
4221
+
4222
+ if not plugins:
4223
+ return None
4224
+
4225
+ return plugins
2907
4226
 
2908
4227
  def delete(self) -> TaskInfo:
2909
4228
  """Deletes the index.
@@ -3039,6 +4358,7 @@ class Index(BaseIndex):
3039
4358
  primary_key: str | None = None,
3040
4359
  *,
3041
4360
  settings: MeilisearchSettings | None = None,
4361
+ plugins: IndexPlugins | None = None,
3042
4362
  ) -> Index:
3043
4363
  """Creates a new index.
3044
4364
 
@@ -3056,6 +4376,7 @@ class Index(BaseIndex):
3056
4376
  adding documents will cause the documents to be re-indexed. Because of this it will be
3057
4377
  faster to update them before adding documents. Defaults to None (i.e. default
3058
4378
  Meilisearch index settings).
4379
+ plugins: Optional plugins can be provided to extend functionality.
3059
4380
 
3060
4381
  Returns:
3061
4382
 
@@ -3089,6 +4410,7 @@ class Index(BaseIndex):
3089
4410
  primary_key=index_dict["primaryKey"],
3090
4411
  created_at=index_dict["createdAt"],
3091
4412
  updated_at=index_dict["updatedAt"],
4413
+ plugins=plugins,
3092
4414
  )
3093
4415
 
3094
4416
  if settings:
@@ -3229,9 +4551,41 @@ class Index(BaseIndex):
3229
4551
  vector=vector,
3230
4552
  )
3231
4553
 
4554
+ if self._pre_search_plugins:
4555
+ Index._run_plugins(
4556
+ self._pre_search_plugins,
4557
+ Event.PRE,
4558
+ query=query,
4559
+ offset=offset,
4560
+ limit=limit,
4561
+ filter=filter,
4562
+ facets=facets,
4563
+ attributes_to_retrieve=attributes_to_retrieve,
4564
+ attributes_to_crop=attributes_to_crop,
4565
+ crop_length=crop_length,
4566
+ attributes_to_highlight=attributes_to_highlight,
4567
+ sort=sort,
4568
+ show_matches_position=show_matches_position,
4569
+ highlight_pre_tag=highlight_pre_tag,
4570
+ highlight_post_tag=highlight_post_tag,
4571
+ crop_marker=crop_marker,
4572
+ matching_strategy=matching_strategy,
4573
+ hits_per_page=hits_per_page,
4574
+ page=page,
4575
+ attributes_to_search_on=attributes_to_search_on,
4576
+ show_ranking_score=show_ranking_score,
4577
+ show_ranking_score_details=show_ranking_score_details,
4578
+ vector=vector,
4579
+ )
4580
+
3232
4581
  response = self._http_requests.post(f"{self._base_url_with_uid}/search", body=body)
4582
+ result = SearchResults(**response.json())
4583
+ if self._post_search_plugins:
4584
+ post = Index._run_plugins(self._post_search_plugins, Event.POST, search_results=result)
4585
+ if post.get("search_result"):
4586
+ result = post["search_result"]
3233
4587
 
3234
- return SearchResults(**response.json())
4588
+ return result
3235
4589
 
3236
4590
  def facet_search(
3237
4591
  self,
@@ -3352,9 +4706,41 @@ class Index(BaseIndex):
3352
4706
  vector=vector,
3353
4707
  )
3354
4708
 
4709
+ if self._pre_facet_search_plugins:
4710
+ Index._run_plugins(
4711
+ self._pre_facet_search_plugins,
4712
+ Event.PRE,
4713
+ query=query,
4714
+ offset=offset,
4715
+ limit=limit,
4716
+ filter=filter,
4717
+ facets=facets,
4718
+ attributes_to_retrieve=attributes_to_retrieve,
4719
+ attributes_to_crop=attributes_to_crop,
4720
+ crop_length=crop_length,
4721
+ attributes_to_highlight=attributes_to_highlight,
4722
+ sort=sort,
4723
+ show_matches_position=show_matches_position,
4724
+ highlight_pre_tag=highlight_pre_tag,
4725
+ highlight_post_tag=highlight_post_tag,
4726
+ crop_marker=crop_marker,
4727
+ matching_strategy=matching_strategy,
4728
+ hits_per_page=hits_per_page,
4729
+ page=page,
4730
+ attributes_to_search_on=attributes_to_search_on,
4731
+ show_ranking_score=show_ranking_score,
4732
+ show_ranking_score_details=show_ranking_score_details,
4733
+ vector=vector,
4734
+ )
4735
+
3355
4736
  response = self._http_requests.post(f"{self._base_url_with_uid}/facet-search", body=body)
4737
+ result = FacetSearchResults(**response.json())
4738
+ if self._post_facet_search_plugins:
4739
+ post = Index._run_plugins(self._post_facet_search_plugins, Event.POST, result=result)
4740
+ if isinstance(post["generic_result"], FacetSearchResults):
4741
+ result = post["generic_result"]
3356
4742
 
3357
- return FacetSearchResults(**response.json())
4743
+ return result
3358
4744
 
3359
4745
  def get_document(self, document_id: str) -> JsonDict:
3360
4746
  """Get one document with given document identifier.
@@ -3477,9 +4863,24 @@ class Index(BaseIndex):
3477
4863
  else:
3478
4864
  url = self._documents_url
3479
4865
 
4866
+ if self._pre_add_documents_plugins:
4867
+ pre = Index._run_plugins(
4868
+ self._pre_add_documents_plugins,
4869
+ Event.PRE,
4870
+ documents=documents,
4871
+ primary_key=primary_key,
4872
+ )
4873
+ if pre.get("document_result"):
4874
+ documents = pre["document_result"]
4875
+
3480
4876
  response = self._http_requests.post(url, documents)
4877
+ result = TaskInfo(**response.json())
4878
+ if self._post_add_documents_plugins:
4879
+ post = Index._run_plugins(self._post_add_documents_plugins, Event.POST, result=result)
4880
+ if isinstance(post.get("generic_result"), TaskInfo):
4881
+ result = post["generic_result"]
3481
4882
 
3482
- return TaskInfo(**response.json())
4883
+ return result
3483
4884
 
3484
4885
  def add_documents_in_batches(
3485
4886
  self,
@@ -3866,9 +5267,26 @@ class Index(BaseIndex):
3866
5267
  else:
3867
5268
  url = self._documents_url
3868
5269
 
5270
+ if self._pre_update_documents_plugins:
5271
+ pre = Index._run_plugins(
5272
+ self._pre_update_documents_plugins,
5273
+ Event.PRE,
5274
+ documents=documents,
5275
+ primary_key=primary_key,
5276
+ )
5277
+ if pre.get("document_result"):
5278
+ documents = pre["document_result"]
5279
+
3869
5280
  response = self._http_requests.put(url, documents)
5281
+ result = TaskInfo(**response.json())
5282
+ if self._post_update_documents_plugins:
5283
+ post = Index._run_plugins(
5284
+ self._post_update_documents_plugins, Event.POST, result=result
5285
+ )
5286
+ if isinstance(post.get("generic_result"), TaskInfo):
5287
+ result = post["generic_result"]
3870
5288
 
3871
- return TaskInfo(**response.json())
5289
+ return result
3872
5290
 
3873
5291
  def update_documents_in_batches(
3874
5292
  self,
@@ -4237,9 +5655,19 @@ class Index(BaseIndex):
4237
5655
  >>> index = client.index("movies")
4238
5656
  >>> index.delete_document("1234")
4239
5657
  """
5658
+ if self._pre_delete_document_plugins:
5659
+ Index._run_plugins(
5660
+ self._pre_delete_document_plugins, Event.PRE, document_id=document_id
5661
+ )
5662
+
4240
5663
  response = self._http_requests.delete(f"{self._documents_url}/{document_id}")
5664
+ result = TaskInfo(**response.json())
5665
+ if self._post_delete_document_plugins:
5666
+ post = Index._run_plugins(self._post_delete_document_plugins, Event.POST, result=result)
5667
+ if isinstance(post.get("generic_result"), TaskInfo):
5668
+ result = post["generic_result"]
4241
5669
 
4242
- return TaskInfo(**response.json())
5670
+ return result
4243
5671
 
4244
5672
  def delete_documents(self, ids: list[str]) -> TaskInfo:
4245
5673
  """Delete multiple documents from the index.
@@ -4264,9 +5692,19 @@ class Index(BaseIndex):
4264
5692
  >>> index = client.index("movies")
4265
5693
  >>> index.delete_documents(["1234", "5678"])
4266
5694
  """
5695
+ if self._pre_delete_documents_plugins:
5696
+ Index._run_plugins(self._pre_delete_documents_plugins, Event.PRE, ids=ids)
5697
+
4267
5698
  response = self._http_requests.post(f"{self._documents_url}/delete-batch", ids)
5699
+ result = TaskInfo(**response.json())
5700
+ if self._post_delete_documents_plugins:
5701
+ post = Index._run_plugins(
5702
+ self._post_delete_documents_plugins, Event.POST, result=result
5703
+ )
5704
+ if isinstance(post.get("generic_result"), TaskInfo):
5705
+ result = post["generic_result"]
4268
5706
 
4269
- return TaskInfo(**response.json())
5707
+ return result
4270
5708
 
4271
5709
  def delete_documents_by_filter(self, filter: Filter) -> TaskInfo:
4272
5710
  """Delete documents from the index by filter.
@@ -4291,11 +5729,23 @@ class Index(BaseIndex):
4291
5729
  >>> index = client.index("movies")
4292
5730
  >>> index.delete_documents_by_filter("genre=horor"))
4293
5731
  """
5732
+ if self._pre_delete_documents_by_filter_plugins:
5733
+ Index._run_plugins(
5734
+ self._pre_delete_documents_by_filter_plugins, Event.PRE, filter=filter
5735
+ )
5736
+
4294
5737
  response = self._http_requests.post(
4295
5738
  f"{self._documents_url}/delete", body={"filter": filter}
4296
5739
  )
5740
+ result = TaskInfo(**response.json())
5741
+ if self._post_delete_documents_by_filter_plugins:
5742
+ post = Index._run_plugins(
5743
+ self._post_delete_documents_by_filter_plugins, Event.POST, result=result
5744
+ )
5745
+ if isinstance(post.get("generic_result"), TaskInfo):
5746
+ result = post["generic_result"]
4297
5747
 
4298
- return TaskInfo(**response.json())
5748
+ return result
4299
5749
 
4300
5750
  def delete_documents_in_batches_by_filter(
4301
5751
  self, filters: list[str | list[str | list[str]]]
@@ -4348,9 +5798,19 @@ class Index(BaseIndex):
4348
5798
  >>> index = client.index("movies")
4349
5799
  >>> index.delete_all_document()
4350
5800
  """
5801
+ if self._pre_delete_all_documents_plugins:
5802
+ Index._run_plugins(self._pre_delete_all_documents_plugins, Event.PRE)
5803
+
4351
5804
  response = self._http_requests.delete(self._documents_url)
5805
+ result = TaskInfo(**response.json())
5806
+ if self._post_delete_all_documents_plugins:
5807
+ post = Index._run_plugins(
5808
+ self._post_delete_all_documents_plugins, Event.POST, result=result
5809
+ )
5810
+ if isinstance(post.get("generic_result"), TaskInfo):
5811
+ result = post["generic_result"]
4352
5812
 
4353
- return TaskInfo(**response.json())
5813
+ return result
4354
5814
 
4355
5815
  def get_settings(self) -> MeilisearchSettings:
4356
5816
  """Get settings of the index.
@@ -5544,6 +7004,46 @@ class Index(BaseIndex):
5544
7004
 
5545
7005
  return TaskInfo(**response.json())
5546
7006
 
7007
+ @staticmethod
7008
+ def _run_plugins(
7009
+ plugins: Sequence[Plugin | DocumentPlugin | PostSearchPlugin],
7010
+ event: Event,
7011
+ **kwargs: Any,
7012
+ ) -> dict[str, Any]:
7013
+ results: dict[str, Any] = {
7014
+ "generic_result": None,
7015
+ "document_result": None,
7016
+ "search_result": None,
7017
+ }
7018
+ generic_tasks = []
7019
+ document_tasks = []
7020
+ search_tasks = []
7021
+ for plugin in plugins:
7022
+ if _plugin_has_method(plugin, "run_plugin"):
7023
+ generic_tasks.append(plugin.run_plugin(event=event, **kwargs)) # type: ignore[union-attr]
7024
+ if _plugin_has_method(plugin, "run_document_plugin"):
7025
+ document_tasks.append(
7026
+ plugin.run_document_plugin(event=event, **kwargs) # type: ignore[union-attr]
7027
+ )
7028
+ if _plugin_has_method(plugin, "run_post_search_plugin"):
7029
+ search_tasks.append(
7030
+ plugin.run_post_search_plugin(event=event, **kwargs) # type: ignore[union-attr]
7031
+ )
7032
+
7033
+ if generic_tasks:
7034
+ for result in reversed(generic_tasks):
7035
+ if result:
7036
+ results["generic_result"] = result
7037
+ break
7038
+
7039
+ if document_tasks:
7040
+ results["document_result"] = document_tasks[-1]
7041
+
7042
+ if search_tasks:
7043
+ results["search_result"] = search_tasks[-1]
7044
+
7045
+ return results
7046
+
5547
7047
 
5548
7048
  async def _async_load_documents_from_file(
5549
7049
  file_path: Path | str,
@@ -5587,8 +7087,8 @@ async def _async_load_documents_from_file(
5587
7087
 
5588
7088
 
5589
7089
  def _batch(
5590
- documents: Sequence[Mapping], batch_size: int
5591
- ) -> Generator[Sequence[Mapping], None, None]:
7090
+ documents: Sequence[MutableMapping], batch_size: int
7091
+ ) -> Generator[Sequence[MutableMapping], None, None]:
5592
7092
  total_len = len(documents)
5593
7093
  for i in range(0, total_len, batch_size):
5594
7094
  yield documents[i : i + batch_size]
@@ -5598,6 +7098,22 @@ def _combine_documents(documents: list[list[Any]]) -> list[Any]:
5598
7098
  return [x for y in documents for x in y]
5599
7099
 
5600
7100
 
7101
+ def _plugin_has_method(
7102
+ plugin: AsyncPlugin
7103
+ | AsyncDocumentPlugin
7104
+ | AsyncPostSearchPlugin
7105
+ | Plugin
7106
+ | DocumentPlugin
7107
+ | PostSearchPlugin,
7108
+ method: str,
7109
+ ) -> bool:
7110
+ check = getattr(plugin, method, None)
7111
+ if callable(check):
7112
+ return True
7113
+
7114
+ return False
7115
+
7116
+
5601
7117
  def _load_documents_from_file(
5602
7118
  file_path: Path | str,
5603
7119
  csv_delimiter: str | None = None,