Skip to content

Commit 48b9913

Browse files
authored
Merge pull request #569 from sanders41/meilisearch-1.3.0
Update for Meilisearch 1.3.0
2 parents 59cee9f + 70f95cc commit 48b9913

File tree

7 files changed

+434
-25
lines changed

7 files changed

+434
-25
lines changed

meilisearch_python_async/index.py

Lines changed: 234 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from meilisearch_python_async.errors import InvalidDocumentError, MeilisearchError
1818
from meilisearch_python_async.models.documents import DocumentsInfo
1919
from meilisearch_python_async.models.index import IndexStats
20-
from meilisearch_python_async.models.search import SearchResults
20+
from meilisearch_python_async.models.search import FacetSearchResults, SearchResults
2121
from meilisearch_python_async.models.settings import (
2222
Faceting,
2323
MeilisearchSettings,
@@ -299,6 +299,10 @@ async def search(
299299
matching_strategy: str = "all",
300300
hits_per_page: int | None = None,
301301
page: int | None = None,
302+
attributes_to_search_on: list[str] | None = None,
303+
show_ranking_score: bool = False,
304+
show_ranking_score_details: bool = False,
305+
vector: list[float] | None = None,
302306
) -> SearchResults:
303307
"""Search the index.
304308
@@ -325,6 +329,24 @@ async def search(
325329
matching_strategy: Specifies the matching strategy Meilisearch should use. Defaults to `all`.
326330
hits_per_page: Sets the number of results returned per page.
327331
page: Sets the specific results page to fetch.
332+
attributes_to_search_on: List of field names. Allow search over a subset of searchable
333+
attributes without modifying the index settings. Defaults to None.
334+
show_ranking_score: If set to True the ranking score will be returned with each document
335+
in the search. Defaults to False.
336+
show_ranking_score_details: If set to True the ranking details will be returned with
337+
each document in the search. Defaults to False. Note: This parameter can only be
338+
used with Meilisearch >= v1.3.0, and is experimental in Meilisearch v1.3.0. In order
339+
to use this feature in Meilisearch v1.3.0 you first need to enable the feature by
340+
sending a PATCH request to /experimental-features with { "scoreDetails": true }.
341+
Because this feature is experimental it may be removed or updated causing breaking
342+
changes in this library without a major version bump so use with caution.
343+
vector: List of vectors for vector search. Defaults to None. Note: This parameter can
344+
only be used with Meilisearch >= v1.3.0, and is experimental in Meilisearch v1.3.0.
345+
In order to use this feature in Meilisearch v1.3.0 you first need to enable the
346+
feature by sending a PATCH request to /experimental-features with
347+
{ "vectorStore": true }. Because this feature is experimental it may be removed or
348+
updated causing breaking changes in this library without a major version bump so use
349+
with caution.
328350
329351
Returns:
330352
@@ -342,30 +364,159 @@ async def search(
342364
>>> index = client.index("movies")
343365
>>> search_results = await index.search("Tron")
344366
"""
345-
body = {
346-
"q": query,
347-
"offset": offset,
348-
"limit": limit,
349-
"filter": filter,
350-
"facets": facets,
351-
"attributesToRetrieve": attributes_to_retrieve,
352-
"attributesToCrop": attributes_to_crop,
353-
"cropLength": crop_length,
354-
"attributesToHighlight": attributes_to_highlight,
355-
"sort": sort,
356-
"showMatchesPosition": show_matches_position,
357-
"highlightPreTag": highlight_pre_tag,
358-
"highlightPostTag": highlight_post_tag,
359-
"cropMarker": crop_marker,
360-
"matchingStrategy": matching_strategy,
361-
"hitsPerPage": hits_per_page,
362-
"page": page,
363-
}
367+
body = _process_search_parameters(
368+
q=query,
369+
offset=offset,
370+
limit=limit,
371+
filter=filter,
372+
facets=facets,
373+
attributes_to_retrieve=attributes_to_retrieve,
374+
attributes_to_crop=attributes_to_crop,
375+
crop_length=crop_length,
376+
attributes_to_highlight=attributes_to_highlight,
377+
sort=sort,
378+
show_matches_position=show_matches_position,
379+
highlight_pre_tag=highlight_pre_tag,
380+
highlight_post_tag=highlight_post_tag,
381+
crop_marker=crop_marker,
382+
matching_strategy=matching_strategy,
383+
hits_per_page=hits_per_page,
384+
page=page,
385+
attributes_to_search_on=attributes_to_search_on,
386+
show_ranking_score=show_ranking_score,
387+
show_ranking_score_details=show_ranking_score_details,
388+
vector=vector,
389+
)
390+
364391
url = f"{self._base_url_with_uid}/search"
365392
response = await self._http_requests.post(url, body=body)
366393

367394
return SearchResults(**response.json())
368395

396+
async def facet_search(
397+
self,
398+
query: str | None = None,
399+
*,
400+
facet_name: str,
401+
facet_query: str,
402+
offset: int = 0,
403+
limit: int = 20,
404+
filter: str | list[str | list[str]] | None = None,
405+
facets: list[str] | None = None,
406+
attributes_to_retrieve: list[str] = ["*"],
407+
attributes_to_crop: list[str] | None = None,
408+
crop_length: int = 200,
409+
attributes_to_highlight: list[str] | None = None,
410+
sort: list[str] | None = None,
411+
show_matches_position: bool = False,
412+
highlight_pre_tag: str = "<em>",
413+
highlight_post_tag: str = "</em>",
414+
crop_marker: str = "...",
415+
matching_strategy: str = "all",
416+
hits_per_page: int | None = None,
417+
page: int | None = None,
418+
attributes_to_search_on: list[str] | None = None,
419+
show_ranking_score: bool = False,
420+
show_ranking_score_details: bool = False,
421+
vector: list[float] | None = None,
422+
) -> FacetSearchResults:
423+
"""Search the index.
424+
425+
Args:
426+
427+
query: String containing the word(s) to search
428+
facet_name: The name of the facet to search
429+
facet_query: The facet search value
430+
offset: Number of documents to skip. Defaults to 0.
431+
limit: Maximum number of documents returned. Defaults to 20.
432+
filter: Filter queries by an attribute value. Defaults to None.
433+
facets: Facets for which to retrieve the matching count. Defaults to None.
434+
attributes_to_retrieve: Attributes to display in the returned documents.
435+
Defaults to ["*"].
436+
attributes_to_crop: Attributes whose values have to be cropped. Defaults to None.
437+
crop_length: The maximun number of words to display. Defaults to 200.
438+
attributes_to_highlight: Attributes whose values will contain highlighted matching terms.
439+
Defaults to None.
440+
sort: Attributes by which to sort the results. Defaults to None.
441+
show_matches_position: Defines whether an object that contains information about the matches should be
442+
returned or not. Defaults to False.
443+
highlight_pre_tag: The opening tag for highlighting text. Defaults to <em>.
444+
highlight_post_tag: The closing tag for highlighting text. Defaults to </em>
445+
crop_marker: Marker to display when the number of words excedes the `crop_length`.
446+
Defaults to ...
447+
matching_strategy: Specifies the matching strategy Meilisearch should use. Defaults to `all`.
448+
hits_per_page: Sets the number of results returned per page.
449+
page: Sets the specific results page to fetch.
450+
attributes_to_search_on: List of field names. Allow search over a subset of searchable
451+
attributes without modifying the index settings. Defaults to None.
452+
show_ranking_score: If set to True the ranking score will be returned with each document
453+
in the search. Defaults to False.
454+
show_ranking_score_details: If set to True the ranking details will be returned with
455+
each document in the search. Defaults to False. Note: This parameter can only be
456+
used with Meilisearch >= v1.3.0, and is experimental in Meilisearch v1.3.0. In order
457+
to use this feature in Meilisearch v1.3.0 you first need to enable the feature by
458+
sending a PATCH request to /experimental-features with { "scoreDetails": true }.
459+
Because this feature is experimental it may be removed or updated causing breaking
460+
changes in this library without a major version bump so use with caution.
461+
vector: List of vectors for vector search. Defaults to None. Note: This parameter can
462+
only be used with Meilisearch >= v1.3.0, and is experimental in Meilisearch v1.3.0.
463+
In order to use this feature in Meilisearch v1.3.0 you first need to enable the
464+
feature by sending a PATCH request to /experimental-features with
465+
{ "vectorStore": true }. Because this feature is experimental it may be removed or
466+
updated causing breaking changes in this library without a major version bump so use
467+
with caution.
468+
469+
Returns:
470+
471+
Results of the search
472+
473+
Raises:
474+
475+
MeilisearchCommunicationError: If there was an error communicating with the server.
476+
MeilisearchApiError: If the Meilisearch API returned an error.
477+
478+
Examples:
479+
480+
>>> from meilisearch_python_async import Client
481+
>>> async with Client("http://localhost.com", "masterKey") as client:
482+
>>> index = client.index("movies")
483+
>>> search_results = await index.search(
484+
>>> "Tron",
485+
>>> facet_name="genre",
486+
>>> facet_query="Sci-fi"
487+
>>> )
488+
"""
489+
body = _process_search_parameters(
490+
q=query,
491+
facet_name=facet_name,
492+
facet_query=facet_query,
493+
offset=offset,
494+
limit=limit,
495+
filter=filter,
496+
facets=facets,
497+
attributes_to_retrieve=attributes_to_retrieve,
498+
attributes_to_crop=attributes_to_crop,
499+
crop_length=crop_length,
500+
attributes_to_highlight=attributes_to_highlight,
501+
sort=sort,
502+
show_matches_position=show_matches_position,
503+
highlight_pre_tag=highlight_pre_tag,
504+
highlight_post_tag=highlight_post_tag,
505+
crop_marker=crop_marker,
506+
matching_strategy=matching_strategy,
507+
hits_per_page=hits_per_page,
508+
page=page,
509+
attributes_to_search_on=attributes_to_search_on,
510+
show_ranking_score=show_ranking_score,
511+
show_ranking_score_details=show_ranking_score_details,
512+
vector=vector,
513+
)
514+
515+
url = f"{self._base_url_with_uid}/facet-search"
516+
response = await self._http_requests.post(url, body=body)
517+
518+
return FacetSearchResults(**response.json())
519+
369520
async def get_document(self, document_id: str) -> dict[str, Any]:
370521
"""Get one document with given document identifier.
371522
@@ -2432,6 +2583,69 @@ async def _load_documents_from_file(
24322583
return documents
24332584

24342585

2586+
def _process_search_parameters(
2587+
*,
2588+
q: str | None = None,
2589+
facet_name: str | None = None,
2590+
facet_query: str | None = None,
2591+
offset: int = 0,
2592+
limit: int = 20,
2593+
filter: str | list[str | list[str]] | None = None,
2594+
facets: list[str] | None = None,
2595+
attributes_to_retrieve: list[str] = ["*"],
2596+
attributes_to_crop: list[str] | None = None,
2597+
crop_length: int = 200,
2598+
attributes_to_highlight: list[str] | None = None,
2599+
sort: list[str] | None = None,
2600+
show_matches_position: bool = False,
2601+
highlight_pre_tag: str = "<em>",
2602+
highlight_post_tag: str = "</em>",
2603+
crop_marker: str = "...",
2604+
matching_strategy: str = "all",
2605+
hits_per_page: int | None = None,
2606+
page: int | None = None,
2607+
attributes_to_search_on: list[str] | None = None,
2608+
show_ranking_score: bool = False,
2609+
show_ranking_score_details: bool = False,
2610+
vector: list[float] | None = None,
2611+
) -> dict[str, Any]:
2612+
body: dict[str, Any] = {
2613+
"q": q,
2614+
"offset": offset,
2615+
"limit": limit,
2616+
"filter": filter,
2617+
"facets": facets,
2618+
"attributesToRetrieve": attributes_to_retrieve,
2619+
"attributesToCrop": attributes_to_crop,
2620+
"cropLength": crop_length,
2621+
"attributesToHighlight": attributes_to_highlight,
2622+
"sort": sort,
2623+
"showMatchesPosition": show_matches_position,
2624+
"highlightPreTag": highlight_pre_tag,
2625+
"highlightPostTag": highlight_post_tag,
2626+
"cropMarker": crop_marker,
2627+
"matchingStrategy": matching_strategy,
2628+
"hitsPerPage": hits_per_page,
2629+
"page": page,
2630+
"attributesToSearchOn": attributes_to_search_on,
2631+
"showRankingScore": show_ranking_score,
2632+
}
2633+
2634+
if facet_name:
2635+
body["facetName"] = facet_name
2636+
2637+
if facet_query:
2638+
body["facetQuery"] = facet_query
2639+
2640+
if show_ranking_score_details:
2641+
body["showRankingScoreDetails"] = show_ranking_score_details
2642+
2643+
if vector:
2644+
body["vector"] = vector
2645+
2646+
return body
2647+
2648+
24352649
def _validate_file_type(file_path: Path) -> None:
24362650
if file_path.suffix not in (".json", ".csv", ".ndjson"):
24372651
raise MeilisearchError("File must be a json, ndjson, or csv file")

meilisearch_python_async/models/search.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
from pydantic import Field
55

66

7+
class FacetHits(CamelBase):
8+
value: str
9+
count: int
10+
11+
12+
class FacetSearchResults(CamelBase):
13+
facet_hits: List[FacetHits]
14+
facet_query: str
15+
processing_time_ms: int
16+
17+
718
class SearchParams(CamelBase):
819
index_uid: str
920
query: Optional[str] = Field(None, alias="q")
@@ -23,6 +34,10 @@ class SearchParams(CamelBase):
2334
matching_strategy: str = "all"
2435
hits_per_page: Optional[int] = None
2536
page: Optional[int] = None
37+
attributes_to_search_on: Optional[List[str]] = None
38+
show_ranking_score: bool = False
39+
show_ranking_score_details: bool = False
40+
vector: Optional[List[float]] = None
2641

2742

2843
class SearchResults(CamelBase):
@@ -37,6 +52,7 @@ class SearchResults(CamelBase):
3752
total_hits: Optional[int] = None
3853
page: Optional[int] = None
3954
hits_per_page: Optional[int] = None
55+
vector: Optional[List[float]] = None
4056

4157

4258
class SearchResultsWithUID(SearchResults):

meilisearch_python_async/models/settings.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from typing import Any, Dict, List, Optional
22

3+
import pydantic
34
from camel_converter.pydantic_base import CamelBase
45

6+
from meilisearch_python_async._utils import is_pydantic_2
7+
58

69
class MinWordSizeForTypos(CamelBase):
710
one_typo: Optional[int] = None
@@ -17,6 +20,35 @@ class TypoTolerance(CamelBase):
1720

1821
class Faceting(CamelBase):
1922
max_values_per_facet: int
23+
sort_facet_values_by: Optional[Dict[str, str]] = None
24+
25+
if is_pydantic_2():
26+
27+
@pydantic.field_validator("sort_facet_values_by") # type: ignore[attr-defined]
28+
@classmethod
29+
def validate_facet_order(cls, v: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
30+
if not v: # pragma: no cover
31+
return None
32+
33+
for _, value in v.items():
34+
if value not in ("alpha", "count"):
35+
raise ValueError('facet_order must be either "alpha" or "count"')
36+
37+
return v
38+
39+
else: # pragma: no cover
40+
41+
@pydantic.validator("sort_facet_values_by") # type: ignore[attr-defined]
42+
@classmethod
43+
def validate_facet_order(cls, v: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
44+
if not v:
45+
return None
46+
47+
for _, value in v.items():
48+
if value not in ("alpha", "count"):
49+
raise ValueError('facet_order must be either "alpha" or "count"')
50+
51+
return v
2052

2153

2254
class Pagination(CamelBase):

meilisearch_python_async/models/task.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def validate_finished_at(cls, v: str) -> Union[datetime, None]:
7171

7272
class TaskStatus(CamelBase):
7373
results: List[TaskResult]
74+
total: int
7475
limit: int
7576
from_: int = Field(..., alias="from")
7677
next: Optional[int] = None

0 commit comments

Comments
 (0)