meilisearch-python-sdk 5.5.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.
Files changed (32) hide show
  1. meilisearch_python_sdk/__init__.py +8 -0
  2. meilisearch_python_sdk/_batch.py +166 -0
  3. meilisearch_python_sdk/_client.py +2468 -0
  4. meilisearch_python_sdk/_http_requests.py +197 -0
  5. meilisearch_python_sdk/_task.py +368 -0
  6. meilisearch_python_sdk/_utils.py +58 -0
  7. meilisearch_python_sdk/_version.py +1 -0
  8. meilisearch_python_sdk/decorators.py +242 -0
  9. meilisearch_python_sdk/errors.py +75 -0
  10. meilisearch_python_sdk/index/__init__.py +4 -0
  11. meilisearch_python_sdk/index/_common.py +296 -0
  12. meilisearch_python_sdk/index/async_index.py +4891 -0
  13. meilisearch_python_sdk/index/index.py +3839 -0
  14. meilisearch_python_sdk/json_handler.py +74 -0
  15. meilisearch_python_sdk/models/__init__.py +0 -0
  16. meilisearch_python_sdk/models/batch.py +58 -0
  17. meilisearch_python_sdk/models/client.py +97 -0
  18. meilisearch_python_sdk/models/documents.py +12 -0
  19. meilisearch_python_sdk/models/health.py +5 -0
  20. meilisearch_python_sdk/models/index.py +46 -0
  21. meilisearch_python_sdk/models/search.py +126 -0
  22. meilisearch_python_sdk/models/settings.py +197 -0
  23. meilisearch_python_sdk/models/task.py +77 -0
  24. meilisearch_python_sdk/models/version.py +9 -0
  25. meilisearch_python_sdk/models/webhook.py +24 -0
  26. meilisearch_python_sdk/plugins.py +124 -0
  27. meilisearch_python_sdk/py.typed +0 -0
  28. meilisearch_python_sdk/types.py +8 -0
  29. meilisearch_python_sdk-5.5.0.dist-info/METADATA +279 -0
  30. meilisearch_python_sdk-5.5.0.dist-info/RECORD +32 -0
  31. meilisearch_python_sdk-5.5.0.dist-info/WHEEL +4 -0
  32. meilisearch_python_sdk-5.5.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,2468 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from ssl import SSLContext
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import jwt
8
+ from camel_converter import dict_to_camel
9
+ from httpx import AsyncClient as HttpxAsyncClient
10
+ from httpx import Client as HttpxClient
11
+
12
+ from meilisearch_python_sdk import _task
13
+ from meilisearch_python_sdk._batch import async_get_batch, async_get_batches
14
+ from meilisearch_python_sdk._batch import get_batch as _get_batch
15
+ from meilisearch_python_sdk._batch import get_batches as _get_batches
16
+ from meilisearch_python_sdk._http_requests import AsyncHttpRequests, HttpRequests
17
+ from meilisearch_python_sdk.errors import InvalidRestriction, MeilisearchApiError
18
+ from meilisearch_python_sdk.index import AsyncIndex, Index
19
+ from meilisearch_python_sdk.json_handler import BuiltinHandler, OrjsonHandler, UjsonHandler
20
+ from meilisearch_python_sdk.models.client import (
21
+ ClientStats,
22
+ Key,
23
+ KeyCreate,
24
+ KeySearch,
25
+ KeyUpdate,
26
+ Network,
27
+ )
28
+ from meilisearch_python_sdk.models.health import Health
29
+ from meilisearch_python_sdk.models.index import IndexInfo
30
+ from meilisearch_python_sdk.models.search import (
31
+ Federation,
32
+ FederationMerged,
33
+ SearchParams,
34
+ SearchResultsFederated,
35
+ SearchResultsWithUID,
36
+ )
37
+ from meilisearch_python_sdk.models.settings import MeilisearchSettings
38
+ from meilisearch_python_sdk.models.task import TaskInfo, TaskResult, TaskStatus
39
+ from meilisearch_python_sdk.models.version import Version
40
+ from meilisearch_python_sdk.models.webhook import Webhook, WebhookCreate, Webhooks, WebhookUpdate
41
+ from meilisearch_python_sdk.plugins import AsyncIndexPlugins, IndexPlugins
42
+ from meilisearch_python_sdk.types import JsonDict
43
+
44
+ if TYPE_CHECKING: # pragma: no cover
45
+ import sys
46
+ from types import TracebackType
47
+
48
+ from meilisearch_python_sdk.models.batch import BatchResult, BatchStatus
49
+ from meilisearch_python_sdk.types import JsonMapping
50
+
51
+ if sys.version_info >= (3, 11):
52
+ from typing import Self
53
+ else:
54
+ from typing_extensions import Self
55
+
56
+
57
+ class BaseClient:
58
+ def __init__(
59
+ self,
60
+ api_key: str | None = None,
61
+ custom_headers: dict[str, str] | None = None,
62
+ json_handler: BuiltinHandler | OrjsonHandler | UjsonHandler | None = None,
63
+ ) -> None:
64
+ self.json_handler = json_handler if json_handler else BuiltinHandler()
65
+ self._headers: dict[str, str] | None = None
66
+ if api_key:
67
+ self._headers = {"Authorization": f"Bearer {api_key}"}
68
+
69
+ if custom_headers:
70
+ if self._headers:
71
+ self._headers.update(custom_headers)
72
+ else:
73
+ self._headers = custom_headers
74
+
75
+ def generate_tenant_token(
76
+ self,
77
+ search_rules: JsonMapping | list[str],
78
+ *,
79
+ api_key: Key,
80
+ expires_at: datetime | None = None,
81
+ ) -> str:
82
+ """Generates a JWT token to use for searching.
83
+
84
+ Args:
85
+ search_rules: Contains restrictions to use for the token. The default rules used for
86
+ the API key used for signing can be used by setting searchRules to ["*"]. If "indexes"
87
+ is included it must be equal to or more restrictive than the key used to generate the
88
+ token.
89
+ api_key: The API key to use to generate the token.
90
+ expires_at: The timepoint at which the token should expire. If value is provided it
91
+ should be a UTC time in the future. Default = None.
92
+
93
+ Returns:
94
+ A JWT token
95
+
96
+ Raises:
97
+ InvalidRestriction: If the restrictions are less strict than the permissions allowed
98
+ in the API key.
99
+ KeyNotFoundError: If no API search key is found.
100
+
101
+ Examples:
102
+ Async:
103
+
104
+ >>> from datetime import datetime, timedelta, timezone
105
+ >>> from meilisearch_python_sdk import AsyncClient
106
+ >>>
107
+ >>> expires_at = datetime.now(tz=timezone.utc) + timedelta(days=7)
108
+ >>>
109
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
110
+ >>> token = client.generate_tenant_token(
111
+ >>> search_rules = ["*"], api_key=api_key, expires_at=expires_at
112
+ >>> )
113
+
114
+ Sync:
115
+
116
+ >>> from datetime import datetime, timedelta, timezone
117
+ >>> from meilisearch_python_sdk import Client
118
+ >>>
119
+ >>> expires_at = datetime.now(tz=timezone.utc) + timedelta(days=7)
120
+ >>>
121
+ >>> with Client("http://localhost.com", "masterKey") as client:
122
+ >>> token = client.generate_tenant_token(
123
+ >>> search_rules = ["*"], api_key=api_key, expires_at=expires_at
124
+ >>> )
125
+ """
126
+ if isinstance(search_rules, dict) and search_rules.get("indexes"):
127
+ for index in search_rules["indexes"]:
128
+ if api_key.indexes != ["*"] and index not in api_key.indexes:
129
+ raise InvalidRestriction(
130
+ "Invalid index. The token cannot be less restrictive than the API key"
131
+ )
132
+
133
+ payload: JsonDict = {"searchRules": search_rules}
134
+
135
+ payload["apiKeyUid"] = api_key.uid
136
+ if expires_at:
137
+ if expires_at <= datetime.now(tz=timezone.utc):
138
+ raise ValueError("expires_at must be a time in the future")
139
+
140
+ payload["exp"] = int(datetime.timestamp(expires_at))
141
+
142
+ return jwt.encode(payload, api_key.key, algorithm="HS256")
143
+
144
+
145
+ class AsyncClient(BaseClient):
146
+ """Async client to connect to the Meilisearch API."""
147
+
148
+ def __init__(
149
+ self,
150
+ url: str,
151
+ api_key: str | None = None,
152
+ *,
153
+ timeout: int | None = None,
154
+ verify: bool | SSLContext = True,
155
+ custom_headers: dict[str, str] | None = None,
156
+ json_handler: BuiltinHandler | OrjsonHandler | UjsonHandler | None = None,
157
+ http2: bool = False,
158
+ ) -> None:
159
+ """Class initializer.
160
+
161
+ Args:
162
+ url: The url to the Meilisearch API (ex: http://localhost:7700)
163
+ api_key: The optional API key for Meilisearch. Defaults to None.
164
+ timeout: The amount of time in seconds that the client will wait for a response before
165
+ timing out. Defaults to None.
166
+ verify: SSL certificates (a.k.a CA bundle) used to
167
+ verify the identity of requested hosts. Either `True` (default CA bundle),
168
+ a path to an SSL certificate file, or `False` (disable verification)
169
+ custom_headers: Custom headers to add when sending data to Meilisearch. Defaults to
170
+ None.
171
+ json_handler: The module to use for json operations. The options are BuiltinHandler
172
+ (uses the json module from the standard library), OrjsonHandler (uses orjson), or
173
+ UjsonHandler (uses ujson). Note that in order use orjson or ujson the corresponding
174
+ extra needs to be included. Default: BuiltinHandler.
175
+ http2: Whether or not to use HTTP/2. Defaults to False.
176
+ """
177
+ super().__init__(api_key, custom_headers, json_handler)
178
+
179
+ self.http_client = HttpxAsyncClient(
180
+ base_url=url, timeout=timeout, headers=self._headers, verify=verify, http2=http2
181
+ )
182
+ self._http_requests = AsyncHttpRequests(self.http_client, json_handler=self.json_handler)
183
+
184
+ async def __aenter__(self) -> Self:
185
+ return self
186
+
187
+ async def __aexit__(
188
+ self,
189
+ et: type[BaseException] | None,
190
+ ev: type[BaseException] | None,
191
+ traceback: TracebackType | None,
192
+ ) -> None:
193
+ await self.aclose()
194
+
195
+ async def aclose(self) -> None:
196
+ """Closes the client.
197
+
198
+ This only needs to be used if the client was not created with a context manager.
199
+ """
200
+ await self.http_client.aclose()
201
+
202
+ async def add_or_update_networks(self, *, network: Network) -> Network:
203
+ """Set or update remote networks.
204
+
205
+ Args:
206
+ network: Information to use for the networks.
207
+
208
+ Returns:
209
+ An instance of Network containing the network information.
210
+
211
+ Raises:
212
+ MeilisearchCommunicationError: If there was an error communicating with the server.
213
+ MeilisearchApiError: If the Meilisearch API returned an error.
214
+
215
+ Examples:
216
+ >>> from meilisearch_python_sdk import AsyncClient
217
+ >>> from meilisearch_python_sdk.models.client import Network, Remote
218
+ >>>
219
+ >>>
220
+ >>> network = Network(
221
+ >>> self_="remote_1",
222
+ >>> remotes={
223
+ >>> "remote_1": {"url": "http://localhost:7700", "searchApiKey": "xxxx"},
224
+ >>> "remote_2": {"url": "http://localhost:7720", "searchApiKey": "xxxx"},
225
+ >>> },
226
+ >>> )
227
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
228
+ >>> response = await client.add_or_update_networks(network=network)
229
+ """
230
+ response = await self._http_requests.patch(
231
+ "network", network.model_dump(by_alias=True, exclude_none=True)
232
+ )
233
+
234
+ return Network(**response.json())
235
+
236
+ async def get_networks(self) -> Network:
237
+ """Fetches the remote-networks
238
+
239
+ Returns:
240
+ An instance of Network containing information about each remote.
241
+
242
+ Raises:
243
+ MeilisearchCommunicationError: If there was an error communicating with the server.
244
+ MeilisearchApiError: If the Meilisearch API returned an error.
245
+
246
+ Examples:
247
+ >>> from meilisearch_python_sdk import AsyncClient
248
+ >>>
249
+ >>>
250
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
251
+ >>> response = await client.get_networks()
252
+ """
253
+ response = await self._http_requests.get("network")
254
+
255
+ return Network(**response.json())
256
+
257
+ async def get_webhooks(self) -> Webhooks:
258
+ """Get all webhooks.
259
+
260
+ Returns:
261
+ An instance of Webhooks containing all configured webhooks.
262
+
263
+ Raises:
264
+ MeilisearchCommunicationError: If there was an error communicating with the server.
265
+ MeilisearchApiError: If the Meilisearch API returned an error.
266
+
267
+ Examples:
268
+ >>> from meilisearch_python_sdk import AsyncClient
269
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
270
+ >>> webhooks = await client.get_webhooks()
271
+ """
272
+ response = await self._http_requests.get("webhooks")
273
+
274
+ return Webhooks(**response.json())
275
+
276
+ async def get_webhook(self, uuid: str) -> Webhook:
277
+ """Get a specific webhook by UUID.
278
+
279
+ Args:
280
+ uuid: The webhook's unique identifier.
281
+
282
+ Returns:
283
+ An instance of Webhook containing the webhook information.
284
+
285
+ Raises:
286
+ MeilisearchCommunicationError: If there was an error communicating with the server.
287
+ MeilisearchApiError: If the Meilisearch API returned an error.
288
+
289
+ Examples:
290
+ >>> from meilisearch_python_sdk import AsyncClient
291
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
292
+ >>> webhook = await client.get_webhook("abc-123")
293
+ """
294
+ response = await self._http_requests.get(f"webhooks/{uuid}")
295
+
296
+ return Webhook(**response.json())
297
+
298
+ async def create_webhook(self, webhook: WebhookCreate) -> Webhook:
299
+ """Create a new webhook.
300
+
301
+ Args:
302
+ webhook: The webhook configuration to create.
303
+
304
+ Returns:
305
+ The created webhook.
306
+
307
+ Raises:
308
+ MeilisearchCommunicationError: If there was an error communicating with the server.
309
+ MeilisearchApiError: If the Meilisearch API returned an error.
310
+
311
+ Examples:
312
+ >>> from meilisearch_python_sdk import AsyncClient
313
+ >>> from meilisearch_python_sdk.models.webhook import WebhookCreate
314
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
315
+ >>> webhook_config = WebhookCreate(
316
+ >>> url="https://example.com/webhook",
317
+ >>> headers={"Authorization": "Bearer token"}
318
+ >>> )
319
+ >>> webhook = await client.create_webhook(webhook_config)
320
+ """
321
+ response = await self._http_requests.post(
322
+ "webhooks", webhook.model_dump(by_alias=True, exclude_none=True)
323
+ )
324
+
325
+ return Webhook(**response.json())
326
+
327
+ async def update_webhook(self, *, uuid: str, webhook: WebhookUpdate) -> Webhook:
328
+ """Update an existing webhook.
329
+
330
+ Args:
331
+ uuid: The webhook's unique identifier.
332
+ webhook: The webhook configuration updates.
333
+
334
+ Returns:
335
+ The updated webhook.
336
+
337
+ Raises:
338
+ MeilisearchCommunicationError: If there was an error communicating with the server.
339
+ MeilisearchApiError: If the Meilisearch API returned an error.
340
+
341
+ Examples:
342
+ >>> from meilisearch_python_sdk import AsyncClient
343
+ >>> from meilisearch_python_sdk.models.webhook import WebhookUpdate
344
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
345
+ >>> webhook_update = WebhookUpdate(url="https://example.com/new-webhook")
346
+ >>> webhook = await client.update_webhook("abc-123", webhook_update)
347
+ """
348
+ response = await self._http_requests.patch(
349
+ f"webhooks/{uuid}", webhook.model_dump(by_alias=True, exclude_none=True)
350
+ )
351
+
352
+ return Webhook(**response.json())
353
+
354
+ async def delete_webhook(self, uuid: str) -> int:
355
+ """Delete a webhook.
356
+
357
+ Args:
358
+ uuid: The webhook's unique identifier.
359
+
360
+ Returns:
361
+ The Response status code. 204 signifies a successful delete.
362
+
363
+ Raises:
364
+ MeilisearchCommunicationError: If there was an error communicating with the server.
365
+ MeilisearchApiError: If the Meilisearch API returned an error.
366
+
367
+ Examples:
368
+ >>> from meilisearch_python_sdk import AsyncClient
369
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
370
+ >>> await client.delete_webhook("abc-123")
371
+ """
372
+ response = await self._http_requests.delete(f"webhooks/{uuid}")
373
+ return response.status_code
374
+
375
+ async def create_dump(self) -> TaskInfo:
376
+ """Trigger the creation of a Meilisearch dump.
377
+
378
+ Returns:
379
+ The details of the task.
380
+ Raises:
381
+ MeilisearchCommunicationError: If there was an error communicating with the server.
382
+ MeilisearchApiError: If the Meilisearch API returned an error.
383
+
384
+ Examples:
385
+ >>> from meilisearch_python_sdk import AsyncClient
386
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
387
+ >>> await client.create_dump()
388
+ """
389
+ response = await self._http_requests.post("dumps")
390
+
391
+ return TaskInfo(**response.json())
392
+
393
+ async def create_index(
394
+ self,
395
+ uid: str,
396
+ primary_key: str | None = None,
397
+ *,
398
+ settings: MeilisearchSettings | None = None,
399
+ wait: bool = True,
400
+ timeout_in_ms: int | None = None,
401
+ plugins: AsyncIndexPlugins | None = None,
402
+ hits_type: Any = JsonDict,
403
+ ) -> AsyncIndex:
404
+ """Creates a new index.
405
+
406
+ Args:
407
+ uid: The index's unique identifier.
408
+ primary_key: The primary key of the documents. Defaults to None.
409
+ settings: Settings for the index. The settings can also be updated independently of
410
+ creating the index. The advantage to updating them here is updating the settings after
411
+ adding documents will cause the documents to be re-indexed. Because of this it will be
412
+ faster to update them before adding documents. Defaults to None (i.e. default
413
+ Meilisearch index settings).
414
+ wait: If set to True and settings are being updated, the index will be returned after
415
+ the settings update has completed. If False it will not wait for settings to complete.
416
+ Default: True
417
+ timeout_in_ms: Amount of time in milliseconds to wait before raising a
418
+ MeilisearchTimeoutError. `None` can also be passed to wait indefinitely. Be aware that
419
+ if the `None` option is used the wait time could be very long. Defaults to None.
420
+ plugins: Optional plugins can be provided to extend functionality.
421
+ hits_type: Allows for a custom type to be passed to use for hits. Defaults to
422
+ JsonDict
423
+
424
+ Returns:
425
+ An instance of AsyncIndex containing the information of the newly created index.
426
+
427
+ Raises:
428
+ MeilisearchCommunicationError: If there was an error communicating with the server.
429
+ MeilisearchApiError: If the Meilisearch API returned an error.
430
+
431
+ Examples:
432
+ >>> from meilisearch_python_sdk import AsyncClient
433
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
434
+ >>> index = await client.create_index("movies")
435
+ """
436
+ return await AsyncIndex.create(
437
+ self.http_client,
438
+ uid,
439
+ primary_key,
440
+ settings=settings,
441
+ wait=wait,
442
+ timeout_in_ms=timeout_in_ms,
443
+ plugins=plugins,
444
+ json_handler=self.json_handler,
445
+ hits_type=hits_type,
446
+ )
447
+
448
+ async def create_snapshot(self) -> TaskInfo:
449
+ """Trigger the creation of a Meilisearch snapshot.
450
+
451
+ Returns:
452
+ The details of the task.
453
+
454
+ Raises:
455
+ MeilisearchCommunicationError: If there was an error communicating with the server.
456
+ MeilisearchApiError: If the Meilisearch API returned an error.
457
+
458
+ Examples:
459
+ >>> from meilisearch_python_sdk import AsyncClient
460
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
461
+ >>> await client.create_snapshot()
462
+ """
463
+ response = await self._http_requests.post("snapshots")
464
+
465
+ return TaskInfo(**response.json())
466
+
467
+ async def delete_index_if_exists(self, uid: str) -> bool:
468
+ """Deletes an index if it already exists.
469
+
470
+ Args:
471
+ uid: The index's unique identifier.
472
+
473
+ Returns:
474
+ True if an index was deleted for False if not.
475
+
476
+ Raises:
477
+ MeilisearchCommunicationError: If there was an error communicating with the server.
478
+ MeilisearchApiError: If the Meilisearch API returned an error.
479
+
480
+ Examples:
481
+ >>> from meilisearch_python_sdk import AsyncClient
482
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
483
+ >>> await client.delete_index_if_exists()
484
+ """
485
+ response = await self._http_requests.delete(f"indexes/{uid}")
486
+ status = await self.wait_for_task(response.json()["taskUid"], timeout_in_ms=100000)
487
+ if status.status == "succeeded":
488
+ return True
489
+ return False
490
+
491
+ async def get_indexes(
492
+ self, *, offset: int | None = None, limit: int | None = None
493
+ ) -> list[AsyncIndex] | None:
494
+ """Get all indexes.
495
+
496
+ Args:
497
+ offset: Number of indexes to skip. The default of None will use the Meilisearch
498
+ default.
499
+ limit: Number of indexes to return. The default of None will use the Meilisearch
500
+ default.
501
+
502
+ Returns:
503
+ A list of all indexes.
504
+
505
+ Raises:
506
+ MeilisearchCommunicationError: If there was an error communicating with the server.
507
+ MeilisearchApiError: If the Meilisearch API returned an error.
508
+
509
+ Examples:
510
+ >>> from meilisearch_python_sdk import AsyncClient
511
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
512
+ >>> indexes = await client.get_indexes()
513
+ """
514
+ url = _build_offset_limit_url("indexes", offset, limit)
515
+ response = await self._http_requests.get(url)
516
+
517
+ if not response.json()["results"]:
518
+ return None
519
+
520
+ return [
521
+ AsyncIndex(
522
+ http_client=self.http_client,
523
+ uid=x["uid"],
524
+ primary_key=x["primaryKey"],
525
+ created_at=x["createdAt"],
526
+ updated_at=x["updatedAt"],
527
+ json_handler=self.json_handler,
528
+ )
529
+ for x in response.json()["results"]
530
+ ]
531
+
532
+ async def get_index(self, uid: str) -> AsyncIndex:
533
+ """Gets a single index based on the uid of the index.
534
+
535
+ Args:
536
+ uid: The index's unique identifier.
537
+
538
+ Returns:
539
+ An AsyncIndex instance containing the information of the fetched index.
540
+
541
+ Raises:
542
+ MeilisearchCommunicationError: If there was an error communicating with the server.
543
+ MeilisearchApiError: If the Meilisearch API returned an error.
544
+
545
+ Examples:
546
+ >>> from meilisearch_python_sdk import AsyncClient
547
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
548
+ >>> index = await client.get_index()
549
+ """
550
+ return await AsyncIndex(self.http_client, uid, json_handler=self.json_handler).fetch_info()
551
+
552
+ def index(self, uid: str, *, plugins: AsyncIndexPlugins | None = None) -> AsyncIndex:
553
+ """Create a local reference to an index identified by UID, without making an HTTP call.
554
+
555
+ Because no network call is made this method is not awaitable.
556
+
557
+ Args:
558
+ uid: The index's unique identifier.
559
+ plugins: Optional plugins can be provided to extend functionality.
560
+
561
+ Returns:
562
+ An AsyncIndex instance.
563
+
564
+ Raises:
565
+ MeilisearchCommunicationError: If there was an error communicating with the server.
566
+ MeilisearchApiError: If the Meilisearch API returned an error.
567
+
568
+ Examples:
569
+ >>> from meilisearch_python_sdk import AsyncClient
570
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
571
+ >>> index = client.index("movies")
572
+ """
573
+ return AsyncIndex(
574
+ self.http_client, uid=uid, plugins=plugins, json_handler=self.json_handler
575
+ )
576
+
577
+ async def get_all_stats(self) -> ClientStats:
578
+ """Get stats for all indexes.
579
+
580
+ Returns:
581
+ Information about database size and all indexes.
582
+ https://docs.meilisearch.com/reference/api/stats.html
583
+
584
+ Raises:
585
+ MeilisearchCommunicationError: If there was an error communicating with the server.
586
+ MeilisearchApiError: If the Meilisearch API returned an error.
587
+
588
+ Examples
589
+ >>> from meilisearch_python_sdk import AsyncClient
590
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
591
+ >>> stats = await client.get_all_stats()
592
+ """
593
+ response = await self._http_requests.get("stats")
594
+
595
+ return ClientStats(**response.json())
596
+
597
+ async def get_or_create_index(
598
+ self,
599
+ uid: str,
600
+ primary_key: str | None = None,
601
+ *,
602
+ plugins: AsyncIndexPlugins | None = None,
603
+ hits_type: Any = JsonDict,
604
+ ) -> AsyncIndex:
605
+ """Get an index, or create it if it doesn't exist.
606
+
607
+ Args:
608
+ uid: The index's unique identifier.
609
+ primary_key: The primary key of the documents. Defaults to None.
610
+ plugins: Optional plugins can be provided to extend functionality.
611
+ hits_type: Allows for a custom type to be passed to use for hits. Defaults to
612
+ JsonDict
613
+
614
+ Returns:
615
+ An instance of AsyncIndex containing the information of the retrieved or newly created index.
616
+
617
+ Raises:
618
+ MeilisearchCommunicationError: If there was an error communicating with the server.
619
+ MeilisearchApiError: If the Meilisearch API returned an error.MeilisearchTimeoutError: If the connection times out.
620
+ MeilisearchTimeoutError: If the connection times out.
621
+
622
+ Examples
623
+ >>> from meilisearch_python_sdk import AsyncClient
624
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
625
+ >>> index = await client.get_or_create_index("movies")
626
+ """
627
+ try:
628
+ index_instance = await self.get_index(uid)
629
+ except MeilisearchApiError as err:
630
+ if "index_not_found" not in err.code:
631
+ raise
632
+ index_instance = await self.create_index(
633
+ uid, primary_key, plugins=plugins, hits_type=hits_type
634
+ )
635
+ return index_instance
636
+
637
+ async def create_key(self, key: KeyCreate) -> Key:
638
+ """Creates a new API key.
639
+
640
+ Args:
641
+ key: The information to use in creating the key. Note that if an expires_at value
642
+ is included it should be in UTC time.
643
+
644
+ Returns:
645
+ The new API key.
646
+
647
+ Raises:
648
+ MeilisearchCommunicationError: If there was an error communicating with the server.
649
+ MeilisearchApiError: If the Meilisearch API returned an error.
650
+
651
+ Examples
652
+ >>> from meilisearch_python_sdk import AsyncClient
653
+ >>> from meilissearch_async_client.models.client import KeyCreate
654
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
655
+ >>> key_info = KeyCreate(
656
+ >>> description="My new key",
657
+ >>> actions=["search"],
658
+ >>> indexes=["movies"],
659
+ >>> )
660
+ >>> keys = await client.create_key(key_info)
661
+ """
662
+ response = await self._http_requests.post(
663
+ "keys", self.json_handler.loads(key.model_dump_json(by_alias=True))
664
+ ) # type: ignore[attr-defined]
665
+
666
+ return Key(**response.json())
667
+
668
+ async def delete_key(self, key: str) -> int:
669
+ """Deletes an API key.
670
+
671
+ Args:
672
+ key: The key or uid to delete.
673
+
674
+ Returns:
675
+ The Response status code. 204 signifies a successful delete.
676
+
677
+ Raises:
678
+ MeilisearchCommunicationError: If there was an error communicating with the server.
679
+ MeilisearchApiError: If the Meilisearch API returned an error.
680
+
681
+ Examples
682
+ >>> from meilisearch_python_sdk import AsyncClient
683
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
684
+ >>> await client.delete_key("abc123")
685
+ """
686
+ response = await self._http_requests.delete(f"keys/{key}")
687
+ return response.status_code
688
+
689
+ async def get_keys(self, *, offset: int | None = None, limit: int | None = None) -> KeySearch:
690
+ """Gets the Meilisearch API keys.
691
+
692
+ Args:
693
+ offset: Number of indexes to skip. The default of None will use the Meilisearch
694
+ default.
695
+ limit: Number of indexes to return. The default of None will use the Meilisearch
696
+ default.
697
+
698
+ Returns:
699
+ API keys.
700
+
701
+ Raises:
702
+ MeilisearchCommunicationError: If there was an error communicating with the server.
703
+ MeilisearchApiError: If the Meilisearch API returned an error.
704
+
705
+ Examples
706
+ from meilisearch_python_sdk import AsyncClient
707
+ async with AsyncClient("http://localhost.com", "masterKey") as client:
708
+ keys = await client.get_keys()
709
+ """
710
+ url = _build_offset_limit_url("keys", offset, limit)
711
+ response = await self._http_requests.get(url)
712
+
713
+ return KeySearch(**response.json())
714
+
715
+ async def get_key(self, key: str) -> Key:
716
+ """Gets information about a specific API key.
717
+
718
+ Args:
719
+ key: The key for which to retrieve the information.
720
+
721
+ Returns:
722
+ The API key, or `None` if the key is not found.
723
+
724
+ Raises:
725
+ MeilisearchCommunicationError: If there was an error communicating with the server.
726
+ MeilisearchApiError: If the Meilisearch API returned an error.
727
+
728
+ Examples
729
+ >>> from meilisearch_python_sdk import AsyncClient
730
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
731
+ >>> keys = await client.get_key("abc123")
732
+ """
733
+ response = await self._http_requests.get(f"keys/{key}")
734
+
735
+ return Key(**response.json())
736
+
737
+ async def update_key(self, key: KeyUpdate) -> Key:
738
+ """Update an API key.
739
+
740
+ Args:
741
+ key: The information to use in updating the key. Note that if an expires_at value
742
+ is included it should be in UTC time.
743
+
744
+ Returns:
745
+ The updated API key.
746
+
747
+ Raises:
748
+ MeilisearchCommunicationError: If there was an error communicating with the server.
749
+ MeilisearchApiError: If the Meilisearch API returned an error.
750
+
751
+ Examples
752
+ >>> from meilisearch_python_sdk import AsyncClient
753
+ >>> from meilissearch_async_client.models.client import KeyUpdate
754
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
755
+ >>> key_info = KeyUpdate(
756
+ key="abc123",
757
+ >>> indexes=["*"],
758
+ >>> )
759
+ >>> keys = await client.update_key(key_info)
760
+ """
761
+ payload = _build_update_key_payload(key, self.json_handler)
762
+ response = await self._http_requests.patch(f"keys/{key.key}", payload)
763
+
764
+ return Key(**response.json())
765
+
766
+ async def multi_search(
767
+ self,
768
+ queries: list[SearchParams],
769
+ *,
770
+ federation: Federation | FederationMerged | None = None,
771
+ hits_type: Any = JsonDict,
772
+ ) -> list[SearchResultsWithUID] | SearchResultsFederated:
773
+ """Multi-index search.
774
+
775
+ Args:
776
+ queries: List of SearchParameters
777
+ federation: If included a single search result with hits built from all queries will
778
+ be returned. This parameter can only be used with Meilisearch >= v1.10.0. Defaults
779
+ to None.
780
+ hits_type: Allows for a custom type to be passed to use for hits. Defaults to
781
+ JsonDict
782
+
783
+ Returns:
784
+ Results of the search
785
+
786
+ Raises:
787
+ MeilisearchCommunicationError: If there was an error communicating with the server.
788
+ MeilisearchApiError: If the Meilisearch API returned an error.
789
+
790
+ Examples
791
+ >>> from meilisearch_python_sdk import AsyncClient
792
+ >>> from meilisearch_python_sdk.models.search import SearchParams
793
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
794
+ >>> queries = [
795
+ >>> SearchParams(index_uid="my_first_index", query"Some search"),
796
+ >>> SearchParams(index_uid="my_second_index", query="Another search")
797
+ >>> ]
798
+ >>> search_results = await client.search(queries)
799
+ """
800
+ url = "multi-search"
801
+ processed_queries = []
802
+ for query in queries:
803
+ q = query.model_dump(by_alias=True)
804
+
805
+ if query.retrieve_vectors is None:
806
+ del q["retrieveVectors"]
807
+
808
+ if federation:
809
+ del q["limit"]
810
+ del q["offset"]
811
+
812
+ if query.media is None:
813
+ del q["media"]
814
+
815
+ processed_queries.append(q)
816
+
817
+ if federation:
818
+ federation_payload = federation.model_dump(by_alias=True)
819
+ if federation.facets_by_index is None:
820
+ del federation_payload["facetsByIndex"]
821
+
822
+ else:
823
+ federation_payload = None
824
+
825
+ response = await self._http_requests.post(
826
+ url,
827
+ body={
828
+ "federation": federation_payload,
829
+ "queries": processed_queries,
830
+ },
831
+ )
832
+
833
+ if federation:
834
+ results = response.json()
835
+ return SearchResultsFederated[hits_type](**results)
836
+
837
+ return [SearchResultsWithUID[hits_type](**x) for x in response.json()["results"]]
838
+
839
+ async def get_raw_index(self, uid: str) -> IndexInfo | None:
840
+ """Gets the index and returns all the index information rather than an AsyncIndex instance.
841
+
842
+ Args:
843
+ uid: The index's unique identifier.
844
+
845
+ Returns:
846
+ Index information rather than an AsyncIndex instance.
847
+
848
+ Raises:
849
+ MeilisearchCommunicationError: If there was an error communicating with the server.
850
+ MeilisearchApiError: If the Meilisearch API returned an error.
851
+
852
+ Examples
853
+ >>> from meilisearch_python_sdk import AsyncClient
854
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
855
+ >>> index = await client.get_raw_index("movies")
856
+ """
857
+ response = await self.http_client.get(f"indexes/{uid}")
858
+
859
+ if response.status_code == 404:
860
+ return None
861
+
862
+ return IndexInfo(**response.json())
863
+
864
+ async def get_raw_indexes(
865
+ self, *, offset: int | None = None, limit: int | None = None
866
+ ) -> list[IndexInfo] | None:
867
+ """Gets all the indexes.
868
+
869
+ Args:
870
+ offset: Number of indexes to skip. The default of None will use the Meilisearch
871
+ default.
872
+ limit: Number of indexes to return. The default of None will use the Meilisearch
873
+ default.
874
+
875
+ Returns all the index information rather than an AsyncIndex instance.
876
+
877
+ Returns:
878
+ A list of the Index information rather than an AsyncIndex instances.
879
+
880
+ Raises:
881
+ MeilisearchCommunicationError: If there was an error communicating with the server.
882
+ MeilisearchApiError: If the Meilisearch API returned an error.
883
+
884
+ Examples
885
+ >>> from meilisearch_python_sdk import AsyncClient
886
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
887
+ >>> index = await client.get_raw_indexes()
888
+ """
889
+ url = _build_offset_limit_url("indexes", offset, limit)
890
+ response = await self._http_requests.get(url)
891
+
892
+ if not response.json()["results"]:
893
+ return None
894
+
895
+ return [IndexInfo(**x) for x in response.json()["results"]]
896
+
897
+ async def get_version(self) -> Version:
898
+ """Get the Meilisearch version.
899
+
900
+ Returns:
901
+ Information about the version of Meilisearch.
902
+
903
+ Raises:
904
+ MeilisearchCommunicationError: If there was an error communicating with the server.
905
+ MeilisearchApiError: If the Meilisearch API returned an error.
906
+
907
+ Examples
908
+ >>> from meilisearch_python_sdk import AsyncClient
909
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
910
+ >>> version = await client.get_version()
911
+ """
912
+ response = await self._http_requests.get("version")
913
+
914
+ return Version(**response.json())
915
+
916
+ async def health(self) -> Health:
917
+ """Get health of the Meilisearch server.
918
+
919
+ Returns:
920
+ The status of the Meilisearch server.
921
+
922
+ Raises:
923
+ MeilisearchCommunicationError: If there was an error communicating with the server.
924
+ MeilisearchApiError: If the Meilisearch API returned an error.
925
+
926
+ Examples
927
+ >>> from meilisearch_python_sdk import AsyncClient
928
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
929
+ >>> health = await client.get_health()
930
+ """
931
+ response = await self._http_requests.get("health")
932
+
933
+ return Health(**response.json())
934
+
935
+ async def swap_indexes(self, indexes: list[tuple[str, str]], rename: bool = False) -> TaskInfo:
936
+ """Swap two indexes.
937
+
938
+ Args:
939
+ indexes: A list of tuples, each tuple should contain the indexes to swap.
940
+ rename: Use rename false if you are swapping two existing indexes. Use rename true if
941
+ the second index in your array does not exist. Default = False
942
+
943
+ Returns:
944
+ The details of the task.
945
+
946
+ Raises:
947
+ MeilisearchCommunicationError: If there was an error communicating with the server.
948
+ MeilisearchApiError: If the Meilisearch API returned an error.
949
+
950
+ Examples
951
+ >>> from meilisearch_python_sdk import AsyncClient
952
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
953
+ >>> index = await client.swap_indexes([("index_a", "index_b")])
954
+ """
955
+ if rename:
956
+ processed_indexes = [{"indexes": x, "rename": True} for x in indexes]
957
+ else:
958
+ processed_indexes = [{"indexes": x} for x in indexes]
959
+ response = await self._http_requests.post("swap-indexes", processed_indexes)
960
+
961
+ return TaskInfo(**response.json())
962
+
963
+ async def get_batch(self, batch_uid: int) -> BatchResult | None:
964
+ return await async_get_batch(self, batch_uid)
965
+
966
+ async def get_batches(
967
+ self,
968
+ *,
969
+ uids: list[int] | None = None,
970
+ batch_uids: list[int] | None = None,
971
+ index_uids: list[int] | None = None,
972
+ statuses: list[str] | None = None,
973
+ types: list[str] | None = None,
974
+ limit: int = 20,
975
+ from_: str | None = None,
976
+ reverse: bool = False,
977
+ before_enqueued_at: datetime | None = None,
978
+ after_enqueued_at: datetime | None = None,
979
+ before_started_at: datetime | None = None,
980
+ after_finished_at: datetime | None = None,
981
+ ) -> BatchStatus:
982
+ return await async_get_batches(
983
+ self,
984
+ uids=uids,
985
+ batch_uids=batch_uids,
986
+ index_uids=index_uids,
987
+ statuses=statuses,
988
+ types=types,
989
+ limit=limit,
990
+ from_=from_,
991
+ reverse=reverse,
992
+ before_enqueued_at=before_enqueued_at,
993
+ after_enqueued_at=after_enqueued_at,
994
+ before_started_at=before_started_at,
995
+ after_finished_at=after_finished_at,
996
+ )
997
+
998
+ async def cancel_tasks(
999
+ self,
1000
+ *,
1001
+ uids: list[int] | None = None,
1002
+ index_uids: list[int] | None = None,
1003
+ statuses: list[str] | None = None,
1004
+ types: list[str] | None = None,
1005
+ before_enqueued_at: datetime | None = None,
1006
+ after_enqueued_at: datetime | None = None,
1007
+ before_started_at: datetime | None = None,
1008
+ after_finished_at: datetime | None = None,
1009
+ ) -> TaskInfo:
1010
+ """Cancel a list of enqueued or processing tasks.
1011
+
1012
+ Defaults to cancelling all tasks.
1013
+
1014
+ Args:
1015
+ uids: A list of task UIDs to cancel.
1016
+ index_uids: A list of index UIDs for which to cancel tasks.
1017
+ statuses: A list of statuses to cancel.
1018
+ types: A list of types to cancel.
1019
+ before_enqueued_at: Cancel tasks that were enqueued before the specified date time.
1020
+ after_enqueued_at: Cancel tasks that were enqueued after the specified date time.
1021
+ before_started_at: Cancel tasks that were started before the specified date time.
1022
+ after_finished_at: Cancel tasks that were finished after the specified date time.
1023
+
1024
+ Returns:
1025
+ The details of the task
1026
+
1027
+ Raises:
1028
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1029
+ MeilisearchApiError: If the Meilisearch API returned an error.
1030
+ MeilisearchTimeoutError: If the connection times out.
1031
+
1032
+ Examples
1033
+ >>> from meilisearch_python_sdk import AsyncClient
1034
+ >>>
1035
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
1036
+ >>> await client.cancel_tasks(uids=[1, 2])
1037
+ """
1038
+ return await _task.async_cancel_tasks(
1039
+ self.http_client,
1040
+ uids=uids,
1041
+ index_uids=index_uids,
1042
+ statuses=statuses,
1043
+ types=types,
1044
+ before_enqueued_at=before_enqueued_at,
1045
+ after_enqueued_at=after_enqueued_at,
1046
+ before_started_at=before_started_at,
1047
+ after_finished_at=after_finished_at,
1048
+ )
1049
+
1050
+ async def get_task(self, task_id: int) -> TaskResult:
1051
+ """Get a single task from it's task id.
1052
+
1053
+ Args:
1054
+ task_id: Identifier of the task to retrieve.
1055
+
1056
+ Returns:
1057
+ Results of a task.
1058
+
1059
+ Raises:
1060
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1061
+ MeilisearchApiError: If the Meilisearch API returned an error.
1062
+ MeilisearchTimeoutError: If the connection times out.
1063
+
1064
+ Examples
1065
+ >>> from meilisearch_python_sdk import AsyncClient
1066
+ >>> from meilisearch_python_sdk.task import get_task
1067
+ >>>
1068
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
1069
+ >>> await client.get_task(client, 1244)
1070
+ """
1071
+ return await _task.async_get_task(self.http_client, task_id=task_id)
1072
+
1073
+ async def delete_tasks(
1074
+ self,
1075
+ *,
1076
+ uids: list[int] | None = None,
1077
+ index_uids: list[int] | None = None,
1078
+ statuses: list[str] | None = None,
1079
+ types: list[str] | None = None,
1080
+ before_enqueued_at: datetime | None = None,
1081
+ after_enqueued_at: datetime | None = None,
1082
+ before_started_at: datetime | None = None,
1083
+ after_finished_at: datetime | None = None,
1084
+ ) -> TaskInfo:
1085
+ """Delete a list of tasks.
1086
+
1087
+ Defaults to deleting all tasks.
1088
+
1089
+ Args:
1090
+ uids: A list of task UIDs to delete.
1091
+ index_uids: A list of index UIDs for which to delete tasks.
1092
+ statuses: A list of statuses to delete.
1093
+ types: A list of types to delete.
1094
+ before_enqueued_at: Delete tasks that were enqueued before the specified date time.
1095
+ after_enqueued_at: Delete tasks that were enqueued after the specified date time.
1096
+ before_started_at: Delete tasks that were started before the specified date time.
1097
+ after_finished_at: Delete tasks that were finished after the specified date time.
1098
+
1099
+ Returns:
1100
+ The details of the task
1101
+
1102
+ Raises:
1103
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1104
+ MeilisearchApiError: If the Meilisearch API returned an error.
1105
+ MeilisearchTimeoutError: If the connection times out.
1106
+
1107
+ Examples
1108
+ >>> from meilisearch_python_sdk import AsyncClient
1109
+ >>> from meilisearch_python_sdk.task import delete_tasks
1110
+ >>>
1111
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
1112
+ >>> await client.delete_tasks(uids=[1, 2])
1113
+ """
1114
+ return await _task.async_delete_tasks(
1115
+ self.http_client,
1116
+ uids=uids,
1117
+ index_uids=index_uids,
1118
+ statuses=statuses,
1119
+ types=types,
1120
+ before_enqueued_at=before_enqueued_at,
1121
+ after_enqueued_at=after_enqueued_at,
1122
+ before_started_at=before_started_at,
1123
+ after_finished_at=after_finished_at,
1124
+ )
1125
+
1126
+ async def get_tasks(
1127
+ self,
1128
+ *,
1129
+ index_ids: list[str] | None = None,
1130
+ types: str | list[str] | None = None,
1131
+ reverse: bool | None = None,
1132
+ ) -> TaskStatus:
1133
+ """Get multiple tasks.
1134
+
1135
+ Args:
1136
+ index_ids: A list of index UIDs for which to get the tasks. If provided this will get the
1137
+ tasks only for the specified indexes, if not all tasks will be returned. Default = None
1138
+ types: Specify specific task types to retrieve. Default = None
1139
+ reverse: If True the tasks will be returned in reverse order. Default = None
1140
+
1141
+ Returns:
1142
+ Task statuses.
1143
+
1144
+ Raises:
1145
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1146
+ MeilisearchApiError: If the Meilisearch API returned an error.
1147
+ MeilisearchTimeoutError: If the connection times out.
1148
+
1149
+ Examples
1150
+ >>> from meilisearch_python_sdk import AsyncClient
1151
+ >>>
1152
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
1153
+ >>> await client.get_tasks()
1154
+ """
1155
+ return await _task.async_get_tasks(
1156
+ self.http_client, index_ids=index_ids, types=types, reverse=reverse
1157
+ )
1158
+
1159
+ async def wait_for_task(
1160
+ self,
1161
+ task_id: int,
1162
+ *,
1163
+ timeout_in_ms: int | None = 5000,
1164
+ interval_in_ms: int = 50,
1165
+ raise_for_status: bool = False,
1166
+ ) -> TaskResult:
1167
+ """Wait until Meilisearch processes a task, and get its status.
1168
+
1169
+ Args:
1170
+ task_id: Identifier of the task to retrieve.
1171
+ timeout_in_ms: Amount of time in milliseconds to wait before raising a
1172
+ MeilisearchTimeoutError. `None` can also be passed to wait indefinitely. Be aware that
1173
+ if the `None` option is used the wait time could be very long. Defaults to 5000.
1174
+ interval_in_ms: Time interval in milliseconds to sleep between requests. Defaults to 50.
1175
+ raise_for_status: When set to `True` a MeilisearchTaskFailedError will be raised if a task
1176
+ has a failed status. Defaults to False.
1177
+
1178
+ Returns:
1179
+ Details of the processed update status.
1180
+
1181
+ Raises:
1182
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1183
+ MeilisearchApiError: If the Meilisearch API returned an error.
1184
+ MeilisearchTimeoutError: If the connection times out.
1185
+ MeilisearchTaskFailedError: If `raise_for_status` is `True` and a task has a failed status.
1186
+
1187
+ Examples
1188
+ >>> from meilisearch_python_sdk import AsyncClient
1189
+ >>> documents = [
1190
+ >>> {"id": 1, "title": "Movie 1", "genre": "comedy"},
1191
+ >>> {"id": 2, "title": "Movie 2", "genre": "drama"},
1192
+ >>> ]
1193
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
1194
+ >>> index = client.index("movies")
1195
+ >>> response = await index.add_documents(documents)
1196
+ >>> await client.wait_for_task(client, response.update_id)
1197
+ """
1198
+ return await _task.async_wait_for_task(
1199
+ self.http_client,
1200
+ task_id=task_id,
1201
+ timeout_in_ms=timeout_in_ms,
1202
+ interval_in_ms=interval_in_ms,
1203
+ raise_for_status=raise_for_status,
1204
+ )
1205
+
1206
+ # No cover because it requires multiple instances of Meilisearch
1207
+ async def transfer_documents( # pragma: no cover
1208
+ self,
1209
+ url: str,
1210
+ *,
1211
+ api_key: str | None = None,
1212
+ payload_size: str | None = None,
1213
+ indexes: JsonMapping | None = None,
1214
+ ) -> TaskInfo:
1215
+ """Transfer settings and documents from one Meilisearch instance to another.
1216
+
1217
+ Args:
1218
+ url: Where to send our settings and documents.
1219
+ api_key: The API key with the rights to send the requests. Usually the master key of
1220
+ the remote machine. Defaults to None.
1221
+ payload_size: Human readable size defining the size of the payloads to send. Defaults
1222
+ to 50 MiB.
1223
+ indexes: A set of patterns of matching the indexes you want to export. Defaults to all
1224
+ indexes without filter.
1225
+
1226
+ Returns:
1227
+ The details of the task.
1228
+
1229
+ Raises:
1230
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1231
+ MeilisearchApiError: If the Meilisearch API returned an error.
1232
+ MeilisearchTimeoutError: If the connection times out.
1233
+
1234
+ Examples
1235
+ >>> from meilisearch_python_sdk import AsyncClient
1236
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
1237
+ >>> await index.transfer_documents(
1238
+ >>> "https://another-instance.com", api_key="otherMasterKey"
1239
+ >>> )
1240
+ """
1241
+ payload: JsonDict = {"url": url}
1242
+
1243
+ if api_key:
1244
+ payload["apiKey"] = api_key
1245
+
1246
+ if payload:
1247
+ payload["payloadSize"] = payload_size
1248
+
1249
+ if indexes:
1250
+ payload["indexes"] = indexes
1251
+
1252
+ response = await self._http_requests.post(url, body=payload)
1253
+
1254
+ return TaskInfo(**response.json())
1255
+
1256
+ async def get_experimental_features(self) -> dict[str, bool]:
1257
+ """Gets all experimental features and if they are enabled or not.
1258
+
1259
+ Returns:
1260
+ The status of the experimental features.
1261
+
1262
+ Raises:
1263
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1264
+ MeilisearchApiError: If the Meilisearch API returned an error.
1265
+ MeilisearchTimeoutError: If the connection times out.
1266
+
1267
+ Examples
1268
+ >>> from meilisearch_python_sdk import AsyncClient
1269
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
1270
+ >>> await index.get_experimental_feature()
1271
+ """
1272
+
1273
+ response = await self._http_requests.get("/experimental-features")
1274
+ return response.json()
1275
+
1276
+ async def update_experimental_features(self, features: dict[str, bool]) -> dict[str, bool]:
1277
+ """Update the status of an experimental feature.
1278
+
1279
+ Args:
1280
+ features: Dictionary of features to enable/disable. The dictionary keys can be in either
1281
+ camel case or snake case, the conversion to the correct type will be handed for you by
1282
+ the program. For example {"logsRoute": True} and {"logs_route": True} will both work.
1283
+
1284
+ Returns:
1285
+ The status of the experimental features.
1286
+
1287
+ Raises:
1288
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1289
+ MeilisearchApiError: If the Meilisearch API returned an error.
1290
+ MeilisearchTimeoutError: If the connection times out.
1291
+
1292
+ Examples
1293
+ >>> from meilisearch_python_sdk import AsyncClient
1294
+ >>> async with AsyncClient("http://localhost.com", "masterKey") as client:
1295
+ >>> await index.update_experimental_features({"logsRoute": True})
1296
+ """
1297
+ payload = dict_to_camel(features)
1298
+ response = await self._http_requests.patch("/experimental-features", body=payload)
1299
+
1300
+ return response.json()
1301
+
1302
+
1303
+ class Client(BaseClient):
1304
+ """client to connect to the Meilisearch API."""
1305
+
1306
+ def __init__(
1307
+ self,
1308
+ url: str,
1309
+ api_key: str | None = None,
1310
+ *,
1311
+ timeout: int | None = None,
1312
+ verify: bool | SSLContext = True,
1313
+ custom_headers: dict[str, str] | None = None,
1314
+ json_handler: BuiltinHandler | OrjsonHandler | UjsonHandler | None = None,
1315
+ http2: bool = False,
1316
+ ) -> None:
1317
+ """Class initializer.
1318
+
1319
+ Args:
1320
+ url: The url to the Meilisearch API (ex: http://localhost:7700)
1321
+ api_key: The optional API key for Meilisearch. Defaults to None.
1322
+ timeout: The amount of time in seconds that the client will wait for a response before
1323
+ timing out. Defaults to None.
1324
+ verify: SSL certificates (a.k.a CA bundle) used to
1325
+ verify the identity of requested hosts. Either `True` (default CA bundle),
1326
+ a path to an SSL certificate file, or `False` (disable verification)
1327
+ custom_headers: Custom headers to add when sending data to Meilisearch. Defaults to
1328
+ None.
1329
+ json_handler: The module to use for json operations. The options are BuiltinHandler
1330
+ (uses the json module from the standard library), OrjsonHandler (uses orjson), or
1331
+ UjsonHandler (uses ujson). Note that in order use orjson or ujson the corresponding
1332
+ extra needs to be included. Default: BuiltinHandler.
1333
+ http2: If set to True, the client will use HTTP/2. Defaults to False.
1334
+ """
1335
+ super().__init__(api_key, custom_headers, json_handler)
1336
+
1337
+ self.http_client = HttpxClient(
1338
+ base_url=url, timeout=timeout, headers=self._headers, verify=verify, http2=http2
1339
+ )
1340
+
1341
+ self._http_requests = HttpRequests(self.http_client, json_handler=self.json_handler)
1342
+
1343
+ def __enter__(self) -> Self:
1344
+ return self
1345
+
1346
+ def __exit__(
1347
+ self,
1348
+ et: type[BaseException] | None,
1349
+ ev: type[BaseException] | None,
1350
+ traceback: TracebackType | None,
1351
+ ) -> None:
1352
+ self.close()
1353
+
1354
+ def close(self) -> None:
1355
+ """Closes the client.
1356
+
1357
+ This only needs to be used if the client was not created with a context manager.
1358
+ """
1359
+ self.http_client.close()
1360
+
1361
+ def add_or_update_networks(self, *, network: Network) -> Network:
1362
+ """Set or update remote networks.
1363
+
1364
+ Args:
1365
+ network: Information to use for the networks.
1366
+
1367
+ Returns:
1368
+ An instance of Network containing the network information.
1369
+
1370
+ Raises:
1371
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1372
+ MeilisearchApiError: If the Meilisearch API returned an error.
1373
+
1374
+ Examples:
1375
+ >>> from meilisearch_python_sdk import Client
1376
+ >>> from meilisearch_python_sdk.models.client import Network, Remote
1377
+ >>>
1378
+ >>>
1379
+ >>> network = Network(
1380
+ >>> self_="remote_1",
1381
+ >>> remotes={
1382
+ >>> "remote_1": {"url": "http://localhost:7700", "searchApiKey": "xxxx"},
1383
+ >>> "remote_2": {"url": "http://localhost:7720", "searchApiKey": "xxxx"},
1384
+ >>> },
1385
+ >>> )
1386
+ >>> with Client("http://localhost.com", "masterKey") as client:
1387
+ >>> response = client.add_or_update_networks(network=network)
1388
+ """
1389
+ response = self._http_requests.patch(
1390
+ "network", network.model_dump(by_alias=True, exclude_none=True)
1391
+ )
1392
+
1393
+ return Network(**response.json())
1394
+
1395
+ def get_networks(self) -> Network:
1396
+ """Fetches the remote-networks
1397
+
1398
+ Returns:
1399
+ An instance of Network containing information about each remote.
1400
+
1401
+ Raises:
1402
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1403
+ MeilisearchApiError: If the Meilisearch API returned an error.
1404
+
1405
+ Examples:
1406
+ >>> from meilisearch_python_sdk import AsyncClient
1407
+ >>>
1408
+ >>>
1409
+ >>> with Client("http://localhost.com", "masterKey") as client:
1410
+ >>> response = client.get_networks()
1411
+ """
1412
+ response = self._http_requests.get("network")
1413
+
1414
+ return Network(**response.json())
1415
+
1416
+ def get_webhooks(self) -> Webhooks:
1417
+ """Get all webhooks.
1418
+
1419
+ Returns:
1420
+ An instance of Webhooks containing all configured webhooks.
1421
+
1422
+ Raises:
1423
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1424
+ MeilisearchApiError: If the Meilisearch API returned an error.
1425
+
1426
+ Examples:
1427
+ >>> from meilisearch_python_sdk import Client
1428
+ >>> with Client("http://localhost.com", "masterKey") as client:
1429
+ >>> webhooks = client.get_webhooks()
1430
+ """
1431
+ response = self._http_requests.get("webhooks")
1432
+
1433
+ return Webhooks(**response.json())
1434
+
1435
+ def get_webhook(self, uuid: str) -> Webhook:
1436
+ """Get a specific webhook by UUID.
1437
+
1438
+ Args:
1439
+ uuid: The webhook's unique identifier.
1440
+
1441
+ Returns:
1442
+ An instance of Webhook containing the webhook information.
1443
+
1444
+ Raises:
1445
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1446
+ MeilisearchApiError: If the Meilisearch API returned an error.
1447
+
1448
+ Examples:
1449
+ >>> from meilisearch_python_sdk import Client
1450
+ >>> with Client("http://localhost.com", "masterKey"):
1451
+ >>> webhook = client.get_webhook("abc-123")
1452
+ """
1453
+ response = self._http_requests.get(f"webhooks/{uuid}")
1454
+
1455
+ return Webhook(**response.json())
1456
+
1457
+ def create_webhook(self, webhook: WebhookCreate) -> Webhook:
1458
+ """Create a new webhook.
1459
+
1460
+ Args:
1461
+ webhook: The webhook configuration to create.
1462
+
1463
+ Returns:
1464
+ The created webhook.
1465
+
1466
+ Raises:
1467
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1468
+ MeilisearchApiError: If the Meilisearch API returned an error.
1469
+
1470
+ Examples:
1471
+ >>> from meilisearch_python_sdk import Client
1472
+ >>> from meilisearch_python_sdk.models.webhook import WebhookCreate
1473
+ >>> with Client("http://localhost.com", "masterKey") as client:
1474
+ >>> webhook_config = WebhookCreate(
1475
+ >>> url="https://example.com/webhook",
1476
+ >>> headers={"Authorization": "Bearer token"}
1477
+ >>> )
1478
+ >>> webhook = client.create_webhook(webhook_config)
1479
+ """
1480
+ response = self._http_requests.post(
1481
+ "webhooks", webhook.model_dump(by_alias=True, exclude_none=True)
1482
+ )
1483
+
1484
+ return Webhook(**response.json())
1485
+
1486
+ def update_webhook(self, *, uuid: str, webhook: WebhookUpdate) -> Webhook:
1487
+ """Update an existing webhook.
1488
+
1489
+ Args:
1490
+ uuid: The webhook's unique identifier.
1491
+ webhook: The webhook configuration updates.
1492
+
1493
+ Returns:
1494
+ The updated webhook.
1495
+
1496
+ Raises:
1497
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1498
+ MeilisearchApiError: If the Meilisearch API returned an error.
1499
+
1500
+ Examples:
1501
+ >>> from meilisearch_python_sdk import Client
1502
+ >>> from meilisearch_python_sdk.models.webhook import WebhookUpdate
1503
+ >>> with Client("http://localhost.com", "masterKey") as client:
1504
+ >>> webhook_update = WebhookUpdate(url="https://example.com/new-webhook")
1505
+ >>> webhook = client.update_webhook("abc-123", webhook_update)
1506
+ """
1507
+ response = self._http_requests.patch(
1508
+ f"webhooks/{uuid}", webhook.model_dump(by_alias=True, exclude_none=True)
1509
+ )
1510
+
1511
+ return Webhook(**response.json())
1512
+
1513
+ def delete_webhook(self, uuid: str) -> int:
1514
+ """Delete a webhook.
1515
+
1516
+ Args:
1517
+ uuid: The webhook's unique identifier.
1518
+
1519
+ Returns:
1520
+ The Response status code. 204 signifies a successful delete.
1521
+
1522
+ Raises:
1523
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1524
+ MeilisearchApiError: If the Meilisearch API returned an error.
1525
+
1526
+ Examples:
1527
+ >>> from meilisearch_python_sdk import Client
1528
+ >>> with Client("http://localhost.com", "masterKey") as client:
1529
+ >>> client.delete_webhook("abc-123")
1530
+ """
1531
+ response = self._http_requests.delete(f"webhooks/{uuid}")
1532
+ return response.status_code
1533
+
1534
+ def create_dump(self) -> TaskInfo:
1535
+ """Trigger the creation of a Meilisearch dump.
1536
+
1537
+ Returns:
1538
+ The details of the task.
1539
+
1540
+ Raises:
1541
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1542
+ MeilisearchApiError: If the Meilisearch API returned an error.
1543
+
1544
+ Examples
1545
+ >>> from meilisearch_python_sdk import Client
1546
+ >>> with Client("http://localhost.com", "masterKey") as client:
1547
+ >>> client.create_dump()
1548
+ """
1549
+ response = self._http_requests.post("dumps")
1550
+
1551
+ return TaskInfo(**response.json())
1552
+
1553
+ def create_index(
1554
+ self,
1555
+ uid: str,
1556
+ primary_key: str | None = None,
1557
+ *,
1558
+ settings: MeilisearchSettings | None = None,
1559
+ wait: bool = True,
1560
+ timeout_in_ms: int | None = None,
1561
+ plugins: IndexPlugins | None = None,
1562
+ hits_type: Any = JsonDict,
1563
+ ) -> Index:
1564
+ """Creates a new index.
1565
+
1566
+ Args:
1567
+ uid: The index's unique identifier.
1568
+ primary_key: The primary key of the documents. Defaults to None.
1569
+ settings: Settings for the index. The settings can also be updated independently of
1570
+ creating the index. The advantage to updating them here is updating the settings after
1571
+ adding documents will cause the documents to be re-indexed. Because of this it will be
1572
+ faster to update them before adding documents. Defaults to None (i.e. default
1573
+ Meilisearch index settings).
1574
+ wait: If set to True and settings are being updated, the index will be returned after
1575
+ the settings update has completed. If False it will not wait for settings to complete.
1576
+ Default: True
1577
+ timeout_in_ms: Amount of time in milliseconds to wait before raising a
1578
+ MeilisearchTimeoutError. `None` can also be passed to wait indefinitely. Be aware that
1579
+ if the `None` option is used the wait time could be very long. Defaults to None.
1580
+ plugins: Optional plugins can be provided to extend functionality.
1581
+ hits_type: Allows for a custom type to be passed to use for hits. Defaults to
1582
+ JsonDict
1583
+
1584
+ Returns:
1585
+ An instance of Index containing the information of the newly created index.
1586
+
1587
+ Raises:
1588
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1589
+ MeilisearchApiError: If the Meilisearch API returned an error.
1590
+
1591
+ Examples
1592
+ >>> from meilisearch_python_sdk import Client
1593
+ >>> with Client("http://localhost.com", "masterKey") as client:
1594
+ >>> index = client.create_index("movies")
1595
+ """
1596
+ return Index.create(
1597
+ self.http_client,
1598
+ uid,
1599
+ primary_key,
1600
+ settings=settings,
1601
+ wait=wait,
1602
+ timeout_in_ms=timeout_in_ms,
1603
+ plugins=plugins,
1604
+ json_handler=self.json_handler,
1605
+ hits_type=hits_type,
1606
+ )
1607
+
1608
+ def create_snapshot(self) -> TaskInfo:
1609
+ """Trigger the creation of a Meilisearch snapshot.
1610
+
1611
+ Returns:
1612
+ The details of the task.
1613
+
1614
+ Raises:
1615
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1616
+ MeilisearchApiError: If the Meilisearch API returned an error.
1617
+
1618
+ Examples
1619
+ >>> from meilisearch_python_sdk import Client
1620
+ >>> with Client("http://localhost.com", "masterKey") as client:
1621
+ >>> client.create_snapshot()
1622
+ """
1623
+ response = self._http_requests.post("snapshots")
1624
+
1625
+ return TaskInfo(**response.json())
1626
+
1627
+ def delete_index_if_exists(self, uid: str) -> bool:
1628
+ """Deletes an index if it already exists.
1629
+
1630
+ Args:
1631
+ uid: The index's unique identifier.
1632
+
1633
+ Returns:
1634
+ True if an index was deleted for False if not.
1635
+
1636
+ Raises:
1637
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1638
+ MeilisearchApiError: If the Meilisearch API returned an error.
1639
+
1640
+ Examples
1641
+ >>> from meilisearch_python_sdk import Client
1642
+ >>> with Client("http://localhost.com", "masterKey") as client:
1643
+ >>> client.delete_index_if_exists()
1644
+ """
1645
+ response = self._http_requests.delete(f"indexes/{uid}")
1646
+ status = self.wait_for_task(response.json()["taskUid"], timeout_in_ms=100000)
1647
+ if status.status == "succeeded":
1648
+ return True
1649
+ return False
1650
+
1651
+ def get_indexes(
1652
+ self, *, offset: int | None = None, limit: int | None = None
1653
+ ) -> list[Index] | None:
1654
+ """Get all indexes.
1655
+ Args:
1656
+ offset: Number of indexes to skip. The default of None will use the Meilisearch
1657
+ default.
1658
+ limit: Number of indexes to return. The default of None will use the Meilisearch
1659
+ default.
1660
+
1661
+ Returns:
1662
+ A list of all indexes.
1663
+
1664
+ Raises:
1665
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1666
+ MeilisearchApiError: If the Meilisearch API returned an error.
1667
+
1668
+ Examples
1669
+ >>> from meilisearch_python_sdk import Client
1670
+ >>> with Client("http://localhost.com", "masterKey") as client:
1671
+ >>> indexes = client.get_indexes()
1672
+ """
1673
+ url = _build_offset_limit_url("indexes", offset, limit)
1674
+ response = self._http_requests.get(url)
1675
+
1676
+ if not response.json()["results"]:
1677
+ return None
1678
+
1679
+ return [
1680
+ Index(
1681
+ http_client=self.http_client,
1682
+ uid=x["uid"],
1683
+ primary_key=x["primaryKey"],
1684
+ created_at=x["createdAt"],
1685
+ updated_at=x["updatedAt"],
1686
+ json_handler=self.json_handler,
1687
+ )
1688
+ for x in response.json()["results"]
1689
+ ]
1690
+
1691
+ def get_index(self, uid: str) -> Index:
1692
+ """Gets a single index based on the uid of the index.
1693
+
1694
+ Args:
1695
+ uid: The index's unique identifier.
1696
+
1697
+ Returns:
1698
+ An Index instance containing the information of the fetched index.
1699
+
1700
+ Raises:
1701
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1702
+ MeilisearchApiError: If the Meilisearch API returned an error.
1703
+
1704
+ Examples
1705
+ >>> from meilisearch_python_sdk import Client
1706
+ >>> with Client("http://localhost.com", "masterKey") as client:
1707
+ >>> index = client.get_index()
1708
+ """
1709
+ return Index(self.http_client, uid, json_handler=self.json_handler).fetch_info()
1710
+
1711
+ def index(self, uid: str, *, plugins: IndexPlugins | None = None) -> Index:
1712
+ """Create a local reference to an index identified by UID, without making an HTTP call.
1713
+
1714
+ Args:
1715
+ uid: The index's unique identifier.
1716
+ plugins: Optional plugins can be provided to extend functionality.
1717
+
1718
+ Returns:
1719
+ An Index instance.
1720
+
1721
+ Raises:
1722
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1723
+ MeilisearchApiError: If the Meilisearch API returned an error.
1724
+
1725
+ Examples
1726
+ >>> from meilisearch_python_sdk import Client
1727
+ >>> with Client("http://localhost.com", "masterKey") as client:
1728
+ >>> index = client.index("movies")
1729
+ """
1730
+ return Index(self.http_client, uid=uid, plugins=plugins, json_handler=self.json_handler)
1731
+
1732
+ def get_all_stats(self) -> ClientStats:
1733
+ """Get stats for all indexes.
1734
+
1735
+ Returns:
1736
+ Information about database size and all indexes.
1737
+ https://docs.meilisearch.com/reference/api/stats.html
1738
+
1739
+ Raises:
1740
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1741
+ MeilisearchApiError: If the Meilisearch API returned an error.
1742
+
1743
+ Examples
1744
+ >>> from meilisearch_python_sdk import Client
1745
+ >>> with Client("http://localhost.com", "masterKey") as client:
1746
+ >>> stats = client.get_all_stats()
1747
+ """
1748
+ response = self._http_requests.get("stats")
1749
+
1750
+ return ClientStats(**response.json())
1751
+
1752
+ def get_or_create_index(
1753
+ self,
1754
+ uid: str,
1755
+ primary_key: str | None = None,
1756
+ *,
1757
+ plugins: IndexPlugins | None = None,
1758
+ hits_type: Any = JsonDict,
1759
+ ) -> Index:
1760
+ """Get an index, or create it if it doesn't exist.
1761
+
1762
+ Args:
1763
+ uid: The index's unique identifier.
1764
+ primary_key: The primary key of the documents. Defaults to None.
1765
+ plugins: Optional plugins can be provided to extend functionality.
1766
+ hits_type: Allows for a custom type to be passed to use for hits. Defaults to
1767
+ JsonDict
1768
+
1769
+ Returns:
1770
+ An instance of Index containing the information of the retrieved or newly created index.
1771
+
1772
+ Raises:
1773
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1774
+ MeilisearchApiError: If the Meilisearch API returned an error.MeilisearchTimeoutError: If the connection times out.
1775
+ MeilisearchTimeoutError: If the connection times out.
1776
+
1777
+ Examples
1778
+ >>> from meilisearch_python_sdk import Client
1779
+ >>> with Client("http://localhost.com", "masterKey") as client:
1780
+ >>> index = client.get_or_create_index("movies")
1781
+ """
1782
+ try:
1783
+ index_instance = self.get_index(uid)
1784
+ except MeilisearchApiError as err:
1785
+ if "index_not_found" not in err.code:
1786
+ raise
1787
+ index_instance = self.create_index(
1788
+ uid, primary_key, plugins=plugins, hits_type=hits_type
1789
+ )
1790
+ return index_instance
1791
+
1792
+ def create_key(self, key: KeyCreate) -> Key:
1793
+ """Creates a new API key.
1794
+
1795
+ Args:
1796
+ key: The information to use in creating the key. Note that if an expires_at value
1797
+ is included it should be in UTC time.
1798
+
1799
+ Returns:
1800
+ The new API key.
1801
+
1802
+ Raises:
1803
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1804
+ MeilisearchApiError: If the Meilisearch API returned an error.
1805
+
1806
+ Examples
1807
+ >>> from meilisearch_python_sdk import Client
1808
+ >>> from meilissearch_async_client.models.client import KeyCreate
1809
+ >>> with Client("http://localhost.com", "masterKey") as client:
1810
+ >>> key_info = KeyCreate(
1811
+ >>> description="My new key",
1812
+ >>> actions=["search"],
1813
+ >>> indexes=["movies"],
1814
+ >>> )
1815
+ >>> keys = client.create_key(key_info)
1816
+ """
1817
+ response = self._http_requests.post(
1818
+ "keys", self.json_handler.loads(key.model_dump_json(by_alias=True))
1819
+ ) # type: ignore[attr-defined]
1820
+
1821
+ return Key(**response.json())
1822
+
1823
+ def delete_key(self, key: str) -> int:
1824
+ """Deletes an API key.
1825
+
1826
+ Args:
1827
+ key: The key or uid to delete.
1828
+
1829
+ Returns:
1830
+ The Response status code. 204 signifies a successful delete.
1831
+
1832
+ Raises:
1833
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1834
+ MeilisearchApiError: If the Meilisearch API returned an error.
1835
+
1836
+ Examples
1837
+ >>> from meilisearch_python_sdk import Client
1838
+ >>> with Client("http://localhost.com", "masterKey") as client:
1839
+ >>> client.delete_key("abc123")
1840
+ """
1841
+ response = self._http_requests.delete(f"keys/{key}")
1842
+ return response.status_code
1843
+
1844
+ def get_keys(self, *, offset: int | None = None, limit: int | None = None) -> KeySearch:
1845
+ """Gets the Meilisearch API keys.
1846
+ Args:
1847
+ offset: Number of indexes to skip. The default of None will use the Meilisearch
1848
+ default.
1849
+ limit: Number of indexes to return. The default of None will use the Meilisearch
1850
+ default.
1851
+
1852
+ Returns:
1853
+ API keys.
1854
+
1855
+ Raises:
1856
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1857
+ MeilisearchApiError: If the Meilisearch API returned an error.
1858
+
1859
+ Examples
1860
+ >>> from meilisearch_python_sdk import Client
1861
+ >>> with Client("http://localhost.com", "masterKey") as client:
1862
+ >>> keys = client.get_keys()
1863
+ """
1864
+ url = _build_offset_limit_url("keys", offset, limit)
1865
+ response = self._http_requests.get(url)
1866
+
1867
+ return KeySearch(**response.json())
1868
+
1869
+ def get_key(self, key: str) -> Key:
1870
+ """Gets information about a specific API key.
1871
+
1872
+ Args:
1873
+ key: The key for which to retrieve the information.
1874
+
1875
+ Returns:
1876
+ The API key, or `None` if the key is not found.
1877
+
1878
+ Raises:
1879
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1880
+ MeilisearchApiError: If the Meilisearch API returned an error.
1881
+
1882
+ Examples
1883
+ >>> from meilisearch_python_sdk import Client
1884
+ >>> with Client("http://localhost.com", "masterKey") as client:
1885
+ >>> keys = client.get_key("abc123")
1886
+ """
1887
+ response = self._http_requests.get(f"keys/{key}")
1888
+
1889
+ return Key(**response.json())
1890
+
1891
+ def update_key(self, key: KeyUpdate) -> Key:
1892
+ """Update an API key.
1893
+
1894
+ Args:
1895
+ key: The information to use in updating the key. Note that if an expires_at value
1896
+ is included it should be in UTC time.
1897
+
1898
+ Returns:
1899
+ The updated API key.
1900
+
1901
+ Raises:
1902
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1903
+ MeilisearchApiError: If the Meilisearch API returned an error.
1904
+
1905
+ Examples
1906
+ >>> from meilisearch_python_sdk import Client
1907
+ >>> from meilissearch_async_client.models.client import KeyUpdate
1908
+ >>> with Client("http://localhost.com", "masterKey") as client:
1909
+ >>> key_info = KeyUpdate(
1910
+ key="abc123",
1911
+ >>> indexes=["*"],
1912
+ >>> )
1913
+ >>> keys = client.update_key(key_info)
1914
+ """
1915
+ payload = _build_update_key_payload(key, self.json_handler)
1916
+ response = self._http_requests.patch(f"keys/{key.key}", payload)
1917
+
1918
+ return Key(**response.json())
1919
+
1920
+ def multi_search(
1921
+ self,
1922
+ queries: list[SearchParams],
1923
+ *,
1924
+ federation: Federation | FederationMerged | None = None,
1925
+ hits_type: Any = JsonDict,
1926
+ ) -> list[SearchResultsWithUID] | SearchResultsFederated:
1927
+ """Multi-index search.
1928
+
1929
+ Args:
1930
+ queries: List of SearchParameters
1931
+ federation: If included a single search result with hits built from all queries will
1932
+ be returned. This parameter can only be used with Meilisearch >= v1.10.0. Defaults
1933
+ to None.
1934
+ hits_type: Allows for a custom type to be passed to use for hits. Defaults to
1935
+ JsonDict
1936
+
1937
+ Returns:
1938
+ Results of the search
1939
+
1940
+ Raises:
1941
+ MeilisearchCommunicationError: If there was an error communicating with the server.
1942
+ MeilisearchApiError: If the Meilisearch API returned an error.
1943
+
1944
+ Examples
1945
+ >>> from meilisearch_python_sdk import Client
1946
+ >>> from meilisearch_python_sdk.models.search import SearchParams
1947
+ >>> with Client("http://localhost.com", "masterKey") as client:
1948
+ >>> queries = [
1949
+ >>> SearchParams(index_uid="my_first_index", query"Some search"),
1950
+ >>> SearchParams(index_uid="my_second_index", query="Another search")
1951
+ >>> ]
1952
+ >>> search_results = client.search(queries)
1953
+ """
1954
+ url = "multi-search"
1955
+ processed_queries = []
1956
+ for query in queries:
1957
+ q = query.model_dump(by_alias=True)
1958
+
1959
+ if query.retrieve_vectors is None:
1960
+ del q["retrieveVectors"]
1961
+
1962
+ if federation:
1963
+ del q["limit"]
1964
+ del q["offset"]
1965
+
1966
+ processed_queries.append(q)
1967
+
1968
+ if federation:
1969
+ federation_payload = federation.model_dump(by_alias=True)
1970
+ if federation.facets_by_index is None:
1971
+ del federation_payload["facetsByIndex"]
1972
+
1973
+ else:
1974
+ federation_payload = None
1975
+
1976
+ response = self._http_requests.post(
1977
+ url,
1978
+ body={
1979
+ "federation": federation_payload,
1980
+ "queries": processed_queries,
1981
+ },
1982
+ )
1983
+
1984
+ if federation:
1985
+ results = response.json()
1986
+ return SearchResultsFederated[hits_type](**results)
1987
+
1988
+ return [SearchResultsWithUID[hits_type](**x) for x in response.json()["results"]]
1989
+
1990
+ def get_raw_index(self, uid: str) -> IndexInfo | None:
1991
+ """Gets the index and returns all the index information rather than an Index instance.
1992
+
1993
+ Args:
1994
+ uid: The index's unique identifier.
1995
+
1996
+ Returns:
1997
+ Index information rather than an Index instance.
1998
+
1999
+ Raises:
2000
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2001
+ MeilisearchApiError: If the Meilisearch API returned an error.
2002
+
2003
+ Examples
2004
+ >>> from meilisearch_python_sdk import Client
2005
+ >>> with Client("http://localhost.com", "masterKey") as client:
2006
+ >>> index = client.get_raw_index("movies")
2007
+ """
2008
+ response = self.http_client.get(f"indexes/{uid}")
2009
+
2010
+ if response.status_code == 404:
2011
+ return None
2012
+
2013
+ return IndexInfo(**response.json())
2014
+
2015
+ def get_raw_indexes(
2016
+ self, *, offset: int | None = None, limit: int | None = None
2017
+ ) -> list[IndexInfo] | None:
2018
+ """Gets all the indexes.
2019
+ Args:
2020
+ offset: Number of indexes to skip. The default of None will use the Meilisearch
2021
+ default.
2022
+ limit: Number of indexes to return. The default of None will use the Meilisearch
2023
+ default.
2024
+
2025
+ Returns all the index information rather than an AsyncIndex instance.
2026
+
2027
+ Returns:
2028
+ A list of the Index information rather than an AsyncIndex instances.
2029
+
2030
+ Raises:
2031
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2032
+ MeilisearchApiError: If the Meilisearch API returned an error.
2033
+
2034
+ Examples
2035
+ >>> from meilisearch_python_sdk import Client
2036
+ >>> with Client("http://localhost.com", "masterKey") as client:
2037
+ >>> index = client.get_raw_indexes()
2038
+ """
2039
+ url = _build_offset_limit_url("indexes", offset, limit)
2040
+ response = self._http_requests.get(url)
2041
+
2042
+ if not response.json()["results"]:
2043
+ return None
2044
+
2045
+ return [IndexInfo(**x) for x in response.json()["results"]]
2046
+
2047
+ def get_version(self) -> Version:
2048
+ """Get the Meilisearch version.
2049
+
2050
+ Returns:
2051
+ Information about the version of Meilisearch.
2052
+
2053
+ Raises:
2054
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2055
+ MeilisearchApiError: If the Meilisearch API returned an error.
2056
+
2057
+ Examples
2058
+ >>> from meilisearch_python_sdk import Client
2059
+ >>> with Client("http://localhost.com", "masterKey") as client:
2060
+ >>> version = client.get_version()
2061
+ """
2062
+ response = self._http_requests.get("version")
2063
+
2064
+ return Version(**response.json())
2065
+
2066
+ def health(self) -> Health:
2067
+ """Get health of the Meilisearch server.
2068
+
2069
+ Returns:
2070
+ The status of the Meilisearch server.
2071
+
2072
+ Raises:
2073
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2074
+ MeilisearchApiError: If the Meilisearch API returned an error.
2075
+
2076
+ Examples
2077
+ >>> from meilisearch_python_sdk import Client
2078
+ >>> with Client("http://localhost.com", "masterKey") as client:
2079
+ >>> health = client.get_health()
2080
+ """
2081
+ response = self._http_requests.get("health")
2082
+
2083
+ return Health(**response.json())
2084
+
2085
+ def swap_indexes(self, indexes: list[tuple[str, str]], rename: bool = False) -> TaskInfo:
2086
+ """Swap two indexes.
2087
+
2088
+ Args:
2089
+ indexes: A list of tuples, each tuple should contain the indexes to swap.
2090
+ rename: Use rename false if you are swapping two existing indexes. Use rename true if
2091
+ the second index in your array does not exist. Default = False
2092
+
2093
+ Returns:
2094
+ The details of the task.
2095
+
2096
+ Raises:
2097
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2098
+ MeilisearchApiError: If the Meilisearch API returned an error.
2099
+
2100
+ Examples
2101
+ >>> from meilisearch_python_sdk import Client
2102
+ >>> with Client("http://localhost.com", "masterKey") as client:
2103
+ >>> index = client.swap_indexes([("index_a", "index_b")])
2104
+ """
2105
+ if rename:
2106
+ processed_indexes = [{"indexes": x, "rename": True} for x in indexes]
2107
+ else:
2108
+ processed_indexes = [{"indexes": x} for x in indexes]
2109
+ response = self._http_requests.post("swap-indexes", processed_indexes)
2110
+
2111
+ return TaskInfo(**response.json())
2112
+
2113
+ def get_batch(self, batch_uid: int) -> BatchResult | None:
2114
+ return _get_batch(self, batch_uid)
2115
+
2116
+ def get_batches(
2117
+ self,
2118
+ *,
2119
+ uids: list[int] | None = None,
2120
+ batch_uids: list[int] | None = None,
2121
+ index_uids: list[int] | None = None,
2122
+ statuses: list[str] | None = None,
2123
+ types: list[str] | None = None,
2124
+ limit: int = 20,
2125
+ from_: str | None = None,
2126
+ reverse: bool = False,
2127
+ before_enqueued_at: datetime | None = None,
2128
+ after_enqueued_at: datetime | None = None,
2129
+ before_started_at: datetime | None = None,
2130
+ after_finished_at: datetime | None = None,
2131
+ ) -> BatchStatus:
2132
+ return _get_batches(
2133
+ self,
2134
+ uids=uids,
2135
+ batch_uids=batch_uids,
2136
+ index_uids=index_uids,
2137
+ statuses=statuses,
2138
+ types=types,
2139
+ limit=limit,
2140
+ from_=from_,
2141
+ reverse=reverse,
2142
+ before_enqueued_at=before_enqueued_at,
2143
+ after_enqueued_at=after_enqueued_at,
2144
+ before_started_at=before_started_at,
2145
+ after_finished_at=after_finished_at,
2146
+ )
2147
+
2148
+ def cancel_tasks(
2149
+ self,
2150
+ *,
2151
+ uids: list[int] | None = None,
2152
+ index_uids: list[int] | None = None,
2153
+ statuses: list[str] | None = None,
2154
+ types: list[str] | None = None,
2155
+ before_enqueued_at: datetime | None = None,
2156
+ after_enqueued_at: datetime | None = None,
2157
+ before_started_at: datetime | None = None,
2158
+ after_finished_at: datetime | None = None,
2159
+ ) -> TaskInfo:
2160
+ """Cancel a list of enqueued or processing tasks.
2161
+
2162
+ Defaults to cancelling all tasks.
2163
+
2164
+ Args:
2165
+ uids: A list of task UIDs to cancel.
2166
+ index_uids: A list of index UIDs for which to cancel tasks.
2167
+ statuses: A list of statuses to cancel.
2168
+ types: A list of types to cancel.
2169
+ before_enqueued_at: Cancel tasks that were enqueued before the specified date time.
2170
+ after_enqueued_at: Cancel tasks that were enqueued after the specified date time.
2171
+ before_started_at: Cancel tasks that were started before the specified date time.
2172
+ after_finished_at: Cancel tasks that were finished after the specified date time.
2173
+
2174
+ Returns:
2175
+ The details of the task
2176
+
2177
+ Raises:
2178
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2179
+ MeilisearchApiError: If the Meilisearch API returned an error.
2180
+ MeilisearchTimeoutError: If the connection times out.
2181
+
2182
+ Examples
2183
+ >>> from meilisearch_python_sdk import Client
2184
+ >>> from meilisearch_python_sdk.task import cancel_tasks
2185
+ >>>
2186
+ >>> with Client("http://localhost.com", "masterKey") as client:
2187
+ >>> client.cancel_tasks(uids=[1, 2])
2188
+ """
2189
+ return _task.cancel_tasks(
2190
+ self.http_client,
2191
+ uids=uids,
2192
+ index_uids=index_uids,
2193
+ statuses=statuses,
2194
+ types=types,
2195
+ before_enqueued_at=before_enqueued_at,
2196
+ after_enqueued_at=after_enqueued_at,
2197
+ before_started_at=before_started_at,
2198
+ after_finished_at=after_finished_at,
2199
+ )
2200
+
2201
+ def delete_tasks(
2202
+ self,
2203
+ *,
2204
+ uids: list[int] | None = None,
2205
+ index_uids: list[int] | None = None,
2206
+ statuses: list[str] | None = None,
2207
+ types: list[str] | None = None,
2208
+ before_enqueued_at: datetime | None = None,
2209
+ after_enqueued_at: datetime | None = None,
2210
+ before_started_at: datetime | None = None,
2211
+ after_finished_at: datetime | None = None,
2212
+ ) -> TaskInfo:
2213
+ """Delete a list of tasks.
2214
+
2215
+ Defaults to deleting all tasks.
2216
+
2217
+ Args:
2218
+ uids: A list of task UIDs to delete.
2219
+ index_uids: A list of index UIDs for which to delete tasks.
2220
+ statuses: A list of statuses to delete.
2221
+ types: A list of types to delete.
2222
+ before_enqueued_at: Delete tasks that were enqueued before the specified date time.
2223
+ after_enqueued_at: Delete tasks that were enqueued after the specified date time.
2224
+ before_started_at: Delete tasks that were started before the specified date time.
2225
+ after_finished_at: Delete tasks that were finished after the specified date time.
2226
+
2227
+ Returns:
2228
+ The details of the task
2229
+
2230
+ Raises:
2231
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2232
+ MeilisearchApiError: If the Meilisearch API returned an error.
2233
+ MeilisearchTimeoutError: If the connection times out.
2234
+
2235
+ Examples
2236
+ >>> from meilisearch_python_sdk import Client
2237
+ >>>
2238
+ >>> with Client("http://localhost.com", "masterKey") as client:
2239
+ >>> client.delete_tasks(client, uids=[1, 2])
2240
+ """
2241
+ return _task.delete_tasks(
2242
+ self.http_client,
2243
+ uids=uids,
2244
+ index_uids=index_uids,
2245
+ statuses=statuses,
2246
+ types=types,
2247
+ before_enqueued_at=before_enqueued_at,
2248
+ after_enqueued_at=after_enqueued_at,
2249
+ before_started_at=before_started_at,
2250
+ after_finished_at=after_finished_at,
2251
+ )
2252
+
2253
+ def get_task(self, task_id: int) -> TaskResult:
2254
+ """Get a single task from it's task id.
2255
+
2256
+ Args:
2257
+ task_id: Identifier of the task to retrieve.
2258
+
2259
+ Returns:
2260
+ Results of a task.
2261
+
2262
+ Raises:
2263
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2264
+ MeilisearchApiError: If the Meilisearch API returned an error.
2265
+ MeilisearchTimeoutError: If the connection times out.
2266
+
2267
+ Examples
2268
+ >>> from meilisearch_python_sdk import Client
2269
+ >>>
2270
+ >>> with Client("http://localhost.com", "masterKey") as client:
2271
+ >>> client.get_task(client, 1244)
2272
+ """
2273
+ return _task.get_task(self.http_client, task_id=task_id)
2274
+
2275
+ def get_tasks(
2276
+ self,
2277
+ *,
2278
+ index_ids: list[str] | None = None,
2279
+ types: str | list[str] | None = None,
2280
+ reverse: bool | None = None,
2281
+ ) -> TaskStatus:
2282
+ """Get multiple tasks.
2283
+
2284
+ Args:
2285
+ index_ids: A list of index UIDs for which to get the tasks. If provided this will get the
2286
+ tasks only for the specified indexes, if not all tasks will be returned. Default = None
2287
+ types: Specify specific task types to retrieve. Default = None
2288
+ reverse: If True the tasks will be returned in reverse order. Default = None
2289
+
2290
+ Returns:
2291
+ Task statuses.
2292
+
2293
+ Raises:
2294
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2295
+ MeilisearchApiError: If the Meilisearch API returned an error.
2296
+ MeilisearchTimeoutError: If the connection times out.
2297
+
2298
+ Examples
2299
+ >>> from meilisearch_python_sdk import Client
2300
+ >>>
2301
+ >>> with Client("http://localhost.com", "masterKey") as client:
2302
+ >>> client.get_tasks(client)
2303
+ """
2304
+ return _task.get_tasks(self.http_client, index_ids=index_ids, types=types, reverse=reverse)
2305
+
2306
+ def wait_for_task(
2307
+ self,
2308
+ task_id: int,
2309
+ *,
2310
+ timeout_in_ms: int | None = 5000,
2311
+ interval_in_ms: int = 50,
2312
+ raise_for_status: bool = False,
2313
+ ) -> TaskResult:
2314
+ """Wait until Meilisearch processes a task, and get its status.
2315
+
2316
+ Args:
2317
+ task_id: Identifier of the task to retrieve.
2318
+ timeout_in_ms: Amount of time in milliseconds to wait before raising a
2319
+ MeilisearchTimeoutError. `None` can also be passed to wait indefinitely. Be aware that
2320
+ if the `None` option is used the wait time could be very long. Defaults to 5000.
2321
+ interval_in_ms: Time interval in milliseconds to sleep between requests. Defaults to 50.
2322
+ raise_for_status: When set to `True` a MeilisearchTaskFailedError will be raised if a task
2323
+ has a failed status. Defaults to False.
2324
+
2325
+ Returns:
2326
+ Details of the processed update status.
2327
+
2328
+ Raises:
2329
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2330
+ MeilisearchApiError: If the Meilisearch API returned an error.
2331
+ MeilisearchTimeoutError: If the connection times out.
2332
+ MeilisearchTaskFailedError: If `raise_for_status` is `True` and a task has a failed status.
2333
+
2334
+ Examples
2335
+ >>> from meilisearch_python_sdk import Client
2336
+ >>> documents = [
2337
+ >>> {"id": 1, "title": "Movie 1", "genre": "comedy"},
2338
+ >>> {"id": 2, "title": "Movie 2", "genre": "drama"},
2339
+ >>> ]
2340
+ >>> with Client("http://localhost.com", "masterKey") as client:
2341
+ >>> index = client.index("movies")
2342
+ >>> response = await index.add_documents(documents)
2343
+ >>> client.wait_for_task(response.update_id)
2344
+ """
2345
+ return _task.wait_for_task(
2346
+ self.http_client,
2347
+ task_id=task_id,
2348
+ timeout_in_ms=timeout_in_ms,
2349
+ interval_in_ms=interval_in_ms,
2350
+ raise_for_status=raise_for_status,
2351
+ )
2352
+
2353
+ # No cover because it requires multiple instances of Meilisearch
2354
+ def transfer_documents( # pragma: no cover
2355
+ self,
2356
+ url: str,
2357
+ *,
2358
+ api_key: str | None = None,
2359
+ payload_size: str | None = None,
2360
+ indexes: JsonMapping | None = None,
2361
+ ) -> TaskInfo:
2362
+ """Transfer settings and documents from one Meilisearch instance to another.
2363
+
2364
+ Args:
2365
+ url: Where to send our settings and documents.
2366
+ api_key: The API key with the rights to send the requests. Usually the master key of
2367
+ the remote machine. Defaults to None.
2368
+ payload_size: Human readable size defining the size of the payloads to send. Defaults
2369
+ to 50 MiB.
2370
+ indexes: A set of patterns of matching the indexes you want to export. Defaults to all
2371
+ indexes without filter.
2372
+
2373
+ Returns:
2374
+ The details of the task.
2375
+
2376
+ Raises:
2377
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2378
+ MeilisearchApiError: If the Meilisearch API returned an error.
2379
+ MeilisearchTimeoutError: If the connection times out.
2380
+
2381
+ Examples
2382
+ >>> from meilisearch_python_sdk import Client
2383
+ >>> with Client("http://localhost.com", "masterKey") as client:
2384
+ >>> index.transfer_documents("https://another-instance.com", api_key="otherMasterKey")
2385
+ """
2386
+ payload: JsonDict = {"url": url}
2387
+
2388
+ if api_key:
2389
+ payload["apiKey"] = api_key
2390
+
2391
+ if payload:
2392
+ payload["payloadSize"] = payload_size
2393
+
2394
+ if indexes:
2395
+ payload["indexes"] = indexes
2396
+
2397
+ response = self._http_requests.post(url, body=payload)
2398
+
2399
+ return TaskInfo(**response.json())
2400
+
2401
+ def get_experimental_features(self) -> dict[str, bool]:
2402
+ """Gets all experimental features and if they are enabled or not.
2403
+
2404
+ Returns:
2405
+ The status of the experimental features.
2406
+
2407
+ Raises:
2408
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2409
+ MeilisearchApiError: If the Meilisearch API returned an error.
2410
+ MeilisearchTimeoutError: If the connection times out.
2411
+
2412
+ Examples
2413
+ >>> from meilisearch_python_sdk import Client
2414
+ >>> with Client("http://localhost.com", "masterKey") as client:
2415
+ >>> index.get_experimental_feature()
2416
+ """
2417
+
2418
+ response = self._http_requests.get("/experimental-features")
2419
+ return response.json()
2420
+
2421
+ def update_experimental_features(self, features: dict[str, bool]) -> dict[str, bool]:
2422
+ """Update the status of an experimental feature.
2423
+
2424
+ Args:
2425
+ features: Dictionary of features to enable/disable. The dictionary keys can be in either
2426
+ camel case or snake case, the conversion to the correct type will be handed for you by
2427
+ the program. For example {"logsRoute": True} and {"logs_route": True} will both work.
2428
+
2429
+ Returns:
2430
+ The status of the experimental features.
2431
+
2432
+ Raises:
2433
+ MeilisearchCommunicationError: If there was an error communicating with the server.
2434
+ MeilisearchApiError: If the Meilisearch API returned an error.
2435
+ MeilisearchTimeoutError: If the connection times out.
2436
+
2437
+ Examples
2438
+ >>> from meilisearch_python_sdk import Client
2439
+ >>> with Client("http://localhost.com", "masterKey") as client:
2440
+ >>> index.update_experimental_features({"logsRoute": True})
2441
+ """
2442
+ payload = dict_to_camel(features)
2443
+ response = self._http_requests.patch("/experimental-features", body=payload)
2444
+
2445
+ return response.json()
2446
+
2447
+
2448
+ def _build_offset_limit_url(base: str, offset: int | None, limit: int | None) -> str:
2449
+ if offset is not None and limit is not None:
2450
+ return f"{base}?offset={offset}&limit={limit}"
2451
+ elif offset is not None:
2452
+ return f"{base}?offset={offset}"
2453
+ elif limit is not None:
2454
+ return f"{base}?limit={limit}"
2455
+
2456
+ return base
2457
+
2458
+
2459
+ def _build_update_key_payload(
2460
+ key: KeyUpdate, json_handler: BuiltinHandler | OrjsonHandler | UjsonHandler
2461
+ ) -> JsonDict:
2462
+ # The json_handler.loads(key.json()) is because Pydantic can't serialize a date in a Python dict,
2463
+ # but can when converting to a json string.
2464
+ return { # type: ignore[attr-defined]
2465
+ k: v
2466
+ for k, v in json_handler.loads(key.model_dump_json(by_alias=True)).items()
2467
+ if v is not None and k != "key"
2468
+ }