Skip to content

Commit 5e1f010

Browse files
authored
Merge pull request #1379 from sanders41/meilisearch-1.16
Update for Meilisearch v1.16.0
2 parents 4b1eb97 + 8ff9bb4 commit 5e1f010

File tree

8 files changed

+193
-15
lines changed

8 files changed

+193
-15
lines changed

meilisearch_python_sdk/_client.py

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,9 @@ async def multi_search(
689689
del q["limit"]
690690
del q["offset"]
691691

692+
if query.media is None:
693+
del q["media"]
694+
692695
processed_queries.append(q)
693696

694697
if federation:
@@ -1058,7 +1061,7 @@ async def wait_for_task(
10581061
10591062
Examples
10601063
>>> from meilisearch_python_sdk import AsyncClient
1061-
>>> >>> documents = [
1064+
>>> documents = [
10621065
>>> {"id": 1, "title": "Movie 1", "genre": "comedy"},
10631066
>>> {"id": 2, "title": "Movie 2", "genre": "drama"},
10641067
>>> ]
@@ -1075,6 +1078,56 @@ async def wait_for_task(
10751078
raise_for_status=raise_for_status,
10761079
)
10771080

1081+
# No cover because it requires multiple instances of Meilisearch
1082+
async def transfer_documents( # pragma: no cover
1083+
self,
1084+
url: str,
1085+
*,
1086+
api_key: str | None = None,
1087+
payload_size: str | None = None,
1088+
indexes: JsonMapping | None = None,
1089+
) -> TaskInfo:
1090+
"""Transfer settings and documents from one Meilisearch instance to another.
1091+
1092+
Args:
1093+
url: Where to send our settings and documents.
1094+
api_key: The API key with the rights to send the requests. Usually the master key of
1095+
the remote machine. Defaults to None.
1096+
payload_size: Human readable size defining the size of the payloads to send. Defaults
1097+
to 50 MiB.
1098+
indexes: A set of patterns of matching the indexes you want to export. Defaults to all
1099+
indexes without filter.
1100+
1101+
Returns:
1102+
The details of the task.
1103+
1104+
Raises:
1105+
MeilisearchCommunicationError: If there was an error communicating with the server.
1106+
MeilisearchApiError: If the Meilisearch API returned an error.
1107+
MeilisearchTimeoutError: If the connection times out.
1108+
1109+
Examples
1110+
>>> from meilisearch_python_sdk import AsyncClient
1111+
>>> async with Client("http://localhost.com", "masterKey") as client:
1112+
>>> await index.transfer_documents(
1113+
>>> "https://another-instance.com", api_key="otherMasterKey"
1114+
>>> )
1115+
"""
1116+
payload: JsonDict = {"url": url}
1117+
1118+
if api_key:
1119+
payload["apiKey"] = api_key
1120+
1121+
if payload:
1122+
payload["payloadSize"] = payload_size
1123+
1124+
if indexes:
1125+
payload["indexes"] = indexes
1126+
1127+
response = await self._http_requests.post(url, body=payload)
1128+
1129+
return TaskInfo(**response.json())
1130+
10781131

10791132
class Client(BaseClient):
10801133
"""client to connect to the Meilisearch API."""
@@ -1968,7 +2021,7 @@ def wait_for_task(
19682021
19692022
Examples
19702023
>>> from meilisearch_python_sdk import Client
1971-
>>> >>> documents = [
2024+
>>> documents = [
19722025
>>> {"id": 1, "title": "Movie 1", "genre": "comedy"},
19732026
>>> {"id": 2, "title": "Movie 2", "genre": "drama"},
19742027
>>> ]
@@ -1985,6 +2038,54 @@ def wait_for_task(
19852038
raise_for_status=raise_for_status,
19862039
)
19872040

2041+
# No cover because it requires multiple instances of Meilisearch
2042+
def transfer_documents( # pragma: no cover
2043+
self,
2044+
url: str,
2045+
*,
2046+
api_key: str | None = None,
2047+
payload_size: str | None = None,
2048+
indexes: JsonMapping | None = None,
2049+
) -> TaskInfo:
2050+
"""Transfer settings and documents from one Meilisearch instance to another.
2051+
2052+
Args:
2053+
url: Where to send our settings and documents.
2054+
api_key: The API key with the rights to send the requests. Usually the master key of
2055+
the remote machine. Defaults to None.
2056+
payload_size: Human readable size defining the size of the payloads to send. Defaults
2057+
to 50 MiB.
2058+
indexes: A set of patterns of matching the indexes you want to export. Defaults to all
2059+
indexes without filter.
2060+
2061+
Returns:
2062+
The details of the task.
2063+
2064+
Raises:
2065+
MeilisearchCommunicationError: If there was an error communicating with the server.
2066+
MeilisearchApiError: If the Meilisearch API returned an error.
2067+
MeilisearchTimeoutError: If the connection times out.
2068+
2069+
Examples
2070+
>>> from meilisearch_python_sdk import Client
2071+
>>> client = Client("http://localhost.com", "masterKey")
2072+
>>> index.transfer_documents("https://another-instance.com", api_key="otherMasterKey")
2073+
"""
2074+
payload: JsonDict = {"url": url}
2075+
2076+
if api_key:
2077+
payload["apiKey"] = api_key
2078+
2079+
if payload:
2080+
payload["payloadSize"] = payload_size
2081+
2082+
if indexes:
2083+
payload["indexes"] = indexes
2084+
2085+
response = self._http_requests.post(url, body=payload)
2086+
2087+
return TaskInfo(**response.json())
2088+
19882089

19892090
def _build_offset_limit_url(base: str, offset: int | None, limit: int | None) -> str:
19902091
if offset is not None and limit is not None:

meilisearch_python_sdk/index.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,7 @@ async def search(
725725
hybrid: Hybrid | None = None,
726726
locales: list[str] | None = None,
727727
retrieve_vectors: bool | None = None,
728+
media: JsonMapping | None = None,
728729
) -> SearchResults:
729730
"""Search the index.
730731
@@ -786,6 +787,13 @@ async def search(
786787
locales: Specifies the languages for the search. This parameter can only be used with
787788
Milisearch >= v1.10.0. Defaults to None letting the Meilisearch pick.
788789
retrieve_vectors: Return document vector data with search result.
790+
media: The content of media is used as if it were a document to generate request
791+
fragments from the searchFragments parameter. Defaults to None. This parameter can
792+
only be used with Meilisearch >= v1.16.0. In order to use this feature in
793+
Meilisearch v1.16.0 you first need to enable the feature by sending a PATCH request
794+
to /experimental-features with { "multimodal": true }. Because this feature is
795+
experimental it may be removed or updated causing breaking changes in this library
796+
without a major version bump so use with caution.
789797
790798
Returns:
791799
Results of the search
@@ -830,6 +838,7 @@ async def search(
830838
ranking_score_threshold=ranking_score_threshold,
831839
locales=locales,
832840
retrieve_vectors=retrieve_vectors,
841+
media=media,
833842
)
834843
search_url = f"{self._base_url_with_uid}/search"
835844

@@ -1349,6 +1358,7 @@ async def get_documents(
13491358
fields: list[str] | None = None,
13501359
filter: Filter | None = None,
13511360
retrieve_vectors: bool = False,
1361+
sort: str | None = None,
13521362
) -> DocumentsInfo:
13531363
"""Get a batch documents from the index.
13541364
@@ -1362,6 +1372,7 @@ async def get_documents(
13621372
retrieve_vectors: If set to True the vectors will be returned with each document.
13631373
Defaults to False. Note: This parameter can only be
13641374
used with Meilisearch >= v1.13.0
1375+
sort: Attribute by which to sort the results. Defaults to None.
13651376
13661377
Returns:
13671378
Documents info.
@@ -1382,6 +1393,9 @@ async def get_documents(
13821393
"limit": limit,
13831394
}
13841395

1396+
if sort:
1397+
parameters["sort"] = sort
1398+
13851399
if retrieve_vectors:
13861400
parameters["retrieveVectors"] = "true"
13871401

@@ -5151,6 +5165,7 @@ def search(
51515165
hybrid: Hybrid | None = None,
51525166
locales: list[str] | None = None,
51535167
retrieve_vectors: bool | None = None,
5168+
media: JsonMapping | None = None,
51545169
) -> SearchResults:
51555170
"""Search the index.
51565171
@@ -5212,6 +5227,13 @@ def search(
52125227
locales: Specifies the languages for the search. This parameter can only be used with
52135228
Milisearch >= v1.10.0. Defaults to None letting the Meilisearch pick.
52145229
retrieve_vectors: Return document vector data with search result.
5230+
media: The content of media is used as if it were a document to generate request
5231+
fragments from the searchFragments parameter. Defaults to None. This parameter can
5232+
only be used with Meilisearch >= v1.16.0. In order to use this feature in
5233+
Meilisearch v1.16.0 you first need to enable the feature by sending a PATCH request
5234+
to /experimental-features with { "multimodal": true }. Because this feature is
5235+
experimental it may be removed or updated causing breaking changes in this library
5236+
without a major version bump so use with caution.
52155237
52165238
Returns:
52175239
Results of the search
@@ -5256,6 +5278,7 @@ def search(
52565278
ranking_score_threshold=ranking_score_threshold,
52575279
locales=locales,
52585280
retrieve_vectors=retrieve_vectors,
5281+
media=media,
52595282
)
52605283

52615284
if self._pre_search_plugins:
@@ -5584,6 +5607,7 @@ def get_documents(
55845607
fields: list[str] | None = None,
55855608
filter: Filter | None = None,
55865609
retrieve_vectors: bool = False,
5610+
sort: str | None = None,
55875611
) -> DocumentsInfo:
55885612
"""Get a batch documents from the index.
55895613
@@ -5597,6 +5621,7 @@ def get_documents(
55975621
retrieve_vectors: If set to True the vectors will be returned with each document.
55985622
Defaults to False. Note: This parameter can only be
55995623
used with Meilisearch >= v1.13.0
5624+
sort: Attribute by which to sort the results. Defaults to None.
56005625
56015626
Returns:
56025627
Documents info.
@@ -5617,6 +5642,9 @@ def get_documents(
56175642
"limit": limit,
56185643
}
56195644

5645+
if sort:
5646+
parameters["sort"] = sort
5647+
56205648
if retrieve_vectors:
56215649
parameters["retrieveVectors"] = "true"
56225650

@@ -5633,6 +5661,7 @@ def get_documents(
56335661
parameters["fields"] = fields
56345662

56355663
parameters["filter"] = filter
5664+
56365665
response = self._http_requests.post(f"{self._documents_url}/fetch", body=parameters)
56375666

56385667
return DocumentsInfo(**response.json())
@@ -8390,6 +8419,7 @@ def _process_search_parameters(
83908419
locales: list[str] | None = None,
83918420
retrieve_vectors: bool | None = None,
83928421
exhaustive_facet_count: bool | None = None,
8422+
media: JsonMapping | None = None,
83938423
) -> JsonDict:
83948424
if attributes_to_retrieve is None:
83958425
attributes_to_retrieve = ["*"]
@@ -8444,6 +8474,9 @@ def _process_search_parameters(
84448474
if exhaustive_facet_count is not None:
84458475
body["exhaustivefacetCount"] = exhaustive_facet_count
84468476

8477+
if media is not None:
8478+
body["media"] = media
8479+
84478480
return body
84488481

84498482

meilisearch_python_sdk/models/search.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pydantic import Field, field_validator
77

88
from meilisearch_python_sdk.errors import MeilisearchError
9-
from meilisearch_python_sdk.types import Filter, JsonDict
9+
from meilisearch_python_sdk.types import Filter, JsonDict, JsonMapping
1010

1111
T = TypeVar("T")
1212

@@ -71,6 +71,7 @@ class SearchParams(CamelBase):
7171
hybrid: Hybrid | None = None
7272
locales: list[str] | None = None
7373
retrieve_vectors: bool | None = None
74+
media: JsonMapping | None = None
7475

7576
@field_validator("ranking_score_threshold", mode="before") # type: ignore[attr-defined]
7677
@classmethod

meilisearch_python_sdk/models/settings.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from enum import Enum
44
from typing import Literal
55

6-
import pydantic
76
from camel_converter.pydantic_base import CamelBase
7+
from pydantic import field_validator, model_validator
88

99
from meilisearch_python_sdk.types import JsonDict
1010

@@ -26,7 +26,7 @@ class Faceting(CamelBase):
2626
max_values_per_facet: int
2727
sort_facet_values_by: dict[str, str] | None = None
2828

29-
@pydantic.field_validator("sort_facet_values_by") # type: ignore[attr-defined]
29+
@field_validator("sort_facet_values_by") # type: ignore[attr-defined]
3030
@classmethod
3131
def validate_facet_order(cls, v: dict[str, str] | None) -> dict[str, str] | None:
3232
if not v: # pragma: no cover
@@ -96,6 +96,15 @@ class RestEmbedder(CamelBase):
9696
request: JsonDict
9797
response: JsonDict
9898
binary_quantized: bool | None = None
99+
indexing_fragments: JsonDict | None = None
100+
search_fragment: JsonDict | None = None
101+
102+
@model_validator(mode="after")
103+
def check_document_template(self) -> RestEmbedder:
104+
if self.indexing_fragments is not None and self.document_template is not None:
105+
raise ValueError("document_template must be None when indexing_fragments is set")
106+
107+
return self
99108

100109

101110
class UserProvidedEmbedder(CamelBase):

tests/test_async_documents.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -728,14 +728,14 @@ async def test_get_documents_populated(async_index_with_documents):
728728

729729
async def test_get_documents_offset_optional_params(async_index_with_documents):
730730
index = await async_index_with_documents()
731-
response = await index.get_documents()
732-
assert len(response.results) == 20
731+
update_response = await index.update_sortable_attributes(["title"])
732+
await async_wait_for_task(index.http_client, update_response.task_uid)
733733
response_offset_limit = await index.get_documents(
734-
limit=3, offset=1, fields=["title", "overview"]
734+
limit=3, offset=1, fields=["title", "overview"], sort="title:asc"
735735
)
736+
736737
assert len(response_offset_limit.results) == 3
737-
assert response_offset_limit.results[0]["title"] == response.results[1]["title"]
738-
assert response_offset_limit.results[0]["overview"] == response.results[1]["overview"]
738+
assert ["title", "overview"] == list(response_offset_limit.results[0].keys())
739739

740740

741741
async def test_get_documents_filter(async_index_with_documents):

tests/test_documents.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -648,12 +648,14 @@ def test_get_documents_populated(index_with_documents):
648648

649649
def test_get_documents_offset_optional_params(index_with_documents):
650650
index = index_with_documents()
651-
response = index.get_documents()
652-
assert len(response.results) == 20
653-
response_offset_limit = index.get_documents(limit=3, offset=1, fields=["title", "overview"])
651+
update_response = index.update_sortable_attributes(["title"])
652+
wait_for_task(index.http_client, update_response.task_uid)
653+
response_offset_limit = index.get_documents(
654+
limit=3, offset=1, fields=["title", "overview"], sort="title:asc"
655+
)
656+
654657
assert len(response_offset_limit.results) == 3
655-
assert response_offset_limit.results[0]["title"] == response.results[1]["title"]
656-
assert response_offset_limit.results[0]["overview"] == response.results[1]["overview"]
658+
assert ["title", "overview"] == list(response_offset_limit.results[0].keys())
657659

658660

659661
def test_get_documents_filter(index_with_documents):

tests/test_index.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from meilisearch_python_sdk._http_requests import HttpRequests
55
from meilisearch_python_sdk._task import wait_for_task
66
from meilisearch_python_sdk.errors import MeilisearchApiError
7+
from meilisearch_python_sdk.index import _process_search_parameters
78
from meilisearch_python_sdk.models.settings import (
89
Embedders,
910
Faceting,
@@ -1007,3 +1008,17 @@ def test_reset_prefix_search_opt_out(empty_index):
10071008
result = index.get_settings()
10081009

10091010
assert result.prefix_search == "indexingTime"
1011+
1012+
1013+
def test_process_search_parameters_media():
1014+
expected = {"test": "test"}
1015+
result = _process_search_parameters(media=expected)
1016+
1017+
assert result.get("media") is not None
1018+
assert result["media"] == expected
1019+
1020+
1021+
def test_process_search_parameters_no_media():
1022+
result = _process_search_parameters()
1023+
1024+
assert "media" not in result.keys()

0 commit comments

Comments
 (0)