Skip to content

Commit efcf134

Browse files
committed
Merge remote-tracking branch 'origin/main' into network
2 parents 9b6d4c2 + 8b817e0 commit efcf134

File tree

9 files changed

+190
-25
lines changed

9 files changed

+190
-25
lines changed

.github/workflows/testing.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
- name: Test with pytest
5151
run: just test-parallel-ci
5252
- name: Upload coverage
53-
uses: codecov/codecov-action@v5
53+
uses: codecov/codecov-action@v5.4.0
5454
with:
5555
token: ${{ secrets.CODECOV_TOKEN }}
5656
fail_ci_if_error: true
@@ -90,7 +90,7 @@ jobs:
9090
- name: Test with pytest
9191
run: just test-parallel-ci-http2
9292
- name: Upload coverage
93-
uses: codecov/codecov-action@v5
93+
uses: codecov/codecov-action@v5.4.0
9494
with:
9595
token: ${{ secrets.CODECOV_TOKEN }}
9696
fail_ci_if_error: true
@@ -118,7 +118,7 @@ jobs:
118118
- name: Test with pytest
119119
run: just test-no-parallel-ci
120120
- name: Upload coverage
121-
uses: codecov/codecov-action@v5
121+
uses: codecov/codecov-action@v5.4.0
122122
with:
123123
token: ${{ secrets.CODECOV_TOKEN }}
124124
fail_ci_if_error: true
@@ -158,7 +158,7 @@ jobs:
158158
- name: Test with pytest
159159
run: just test-no-parallel-ci-http2
160160
- name: Upload coverage
161-
uses: codecov/codecov-action@v5
161+
uses: codecov/codecov-action@v5.4.0
162162
with:
163163
token: ${{ secrets.CODECOV_TOKEN }}
164164
fail_ci_if_error: true

meilisearch_python_sdk/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "4.4.0"
1+
VERSION = "4.5.0"

meilisearch_python_sdk/index.py

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
from meilisearch_python_sdk.models.settings import (
3030
Embedders,
3131
Faceting,
32+
FilterableAttributeFeatures,
33+
FilterableAttributes,
3234
HuggingFaceEmbedder,
3335
LocalizedAttributes,
3436
MeilisearchSettings,
@@ -990,6 +992,7 @@ async def facet_search(
990992
vector: list[float] | None = None,
991993
locales: list[str] | None = None,
992994
retrieve_vectors: bool | None = None,
995+
exhaustive_facet_count: bool | None = None,
993996
) -> FacetSearchResults:
994997
"""Search the index.
995998
@@ -1043,6 +1046,9 @@ async def facet_search(
10431046
locales: Specifies the languages for the search. This parameter can only be used with
10441047
Milisearch >= v1.10.0. Defaults to None letting the Meilisearch pick.
10451048
retrieve_vectors: Return document vector data with search result.
1049+
exhaustive_facet_count: forcing the facet search to compute the facet counts the same
1050+
way as the paginated search. This parameter can only be used with Milisearch >=
1051+
v1.14.0. Defaults to None.
10461052
10471053
Returns:
10481054
Results of the search
@@ -1091,6 +1097,7 @@ async def facet_search(
10911097
vector=vector,
10921098
locales=locales,
10931099
retrieve_vectors=retrieve_vectors,
1100+
exhaustive_facet_count=exhaustive_facet_count,
10941101
)
10951102
search_url = f"{self._base_url_with_uid}/facet-search"
10961103

@@ -1120,6 +1127,7 @@ async def facet_search(
11201127
show_ranking_score_details=show_ranking_score_details,
11211128
ranking_score_threshold=ranking_score_threshold,
11221129
vector=vector,
1130+
exhaustive_facet_count=exhaustive_facet_count,
11231131
)
11241132

11251133
if self._concurrent_facet_search_plugins:
@@ -1152,6 +1160,7 @@ async def facet_search(
11521160
show_ranking_score_details=show_ranking_score_details,
11531161
ranking_score_threshold=ranking_score_threshold,
11541162
vector=vector,
1163+
exhaustive_facet_count=exhaustive_facet_count,
11551164
)
11561165
)
11571166

@@ -1195,6 +1204,7 @@ async def facet_search(
11951204
show_ranking_score_details=show_ranking_score_details,
11961205
ranking_score_threshold=ranking_score_threshold,
11971206
vector=vector,
1207+
exhaustive_facet_count=exhaustive_facet_count,
11981208
)
11991209
)
12001210

@@ -3568,11 +3578,11 @@ async def reset_synonyms(self) -> TaskInfo:
35683578

35693579
return TaskInfo(**response.json())
35703580

3571-
async def get_filterable_attributes(self) -> list[str] | None:
3581+
async def get_filterable_attributes(self) -> list[str] | list[FilterableAttributes] | None:
35723582
"""Get filterable attributes of the index.
35733583
35743584
Returns:
3575-
List containing the filterable attributes of the index.
3585+
Filterable attributes of the index.
35763586
35773587
Raises:
35783588
MeilisearchCommunicationError: If there was an error communicating with the server.
@@ -3589,10 +3599,24 @@ async def get_filterable_attributes(self) -> list[str] | None:
35893599
if not response.json():
35903600
return None
35913601

3592-
return response.json()
3602+
response_json = response.json()
3603+
3604+
if isinstance(response_json[0], str):
3605+
return response_json
3606+
3607+
filterable_attributes = []
3608+
for r in response_json:
3609+
filterable_attributes.append(
3610+
FilterableAttributes(
3611+
attribute_patterns=r["attributePatterns"],
3612+
features=FilterableAttributeFeatures(**r["features"]),
3613+
)
3614+
)
3615+
3616+
return filterable_attributes
35933617

35943618
async def update_filterable_attributes(
3595-
self, body: list[str], *, compress: bool = False
3619+
self, body: list[str] | list[FilterableAttributes], *, compress: bool = False
35963620
) -> TaskInfo:
35973621
"""Update filterable attributes of the index.
35983622
@@ -3613,8 +3637,16 @@ async def update_filterable_attributes(
36133637
>>> index = client.index("movies")
36143638
>>> await index.update_filterable_attributes(["genre", "director"])
36153639
"""
3640+
payload: list[str | JsonDict] = []
3641+
3642+
for b in body:
3643+
if isinstance(b, FilterableAttributes):
3644+
payload.append(b.model_dump(by_alias=True))
3645+
else:
3646+
payload.append(b)
3647+
36163648
response = await self._http_requests.put(
3617-
f"{self._settings_url}/filterable-attributes", body, compress=compress
3649+
f"{self._settings_url}/filterable-attributes", payload, compress=compress
36183650
)
36193651

36203652
return TaskInfo(**response.json())
@@ -5292,6 +5324,7 @@ def facet_search(
52925324
vector: list[float] | None = None,
52935325
locales: list[str] | None = None,
52945326
retrieve_vectors: bool | None = None,
5327+
exhaustive_facet_count: bool | None = None,
52955328
) -> FacetSearchResults:
52965329
"""Search the index.
52975330
@@ -5345,6 +5378,9 @@ def facet_search(
53455378
locales: Specifies the languages for the search. This parameter can only be used with
53465379
Milisearch >= v1.10.0. Defaults to None letting the Meilisearch pick.
53475380
retrieve_vectors: Return document vector data with search result.
5381+
exhaustive_facet_count: forcing the facet search to compute the facet counts the same
5382+
way as the paginated search. This parameter can only be used with Milisearch >=
5383+
v1.14.0. Defaults to None.
53485384
53495385
Returns:
53505386
Results of the search
@@ -5393,6 +5429,7 @@ def facet_search(
53935429
vector=vector,
53945430
locales=locales,
53955431
retrieve_vectors=retrieve_vectors,
5432+
exhaustive_facet_count=exhaustive_facet_count,
53965433
)
53975434

53985435
if self._pre_facet_search_plugins:
@@ -5421,6 +5458,7 @@ def facet_search(
54215458
show_ranking_score_details=show_ranking_score_details,
54225459
ranking_score_threshold=ranking_score_threshold,
54235460
vector=vector,
5461+
exhaustive_facet_count=exhaustive_facet_count,
54245462
)
54255463

54265464
response = self._http_requests.post(f"{self._base_url_with_uid}/facet-search", body=body)
@@ -7156,7 +7194,7 @@ def reset_synonyms(self) -> TaskInfo:
71567194

71577195
return TaskInfo(**response.json())
71587196

7159-
def get_filterable_attributes(self) -> list[str] | None:
7197+
def get_filterable_attributes(self) -> list[str] | list[FilterableAttributes] | None:
71607198
"""Get filterable attributes of the index.
71617199
71627200
Returns:
@@ -7177,9 +7215,25 @@ def get_filterable_attributes(self) -> list[str] | None:
71777215
if not response.json():
71787216
return None
71797217

7180-
return response.json()
7218+
response_json = response.json()
7219+
7220+
if isinstance(response_json[0], str):
7221+
return response_json
7222+
7223+
filterable_attributes = []
7224+
for r in response_json:
7225+
filterable_attributes.append(
7226+
FilterableAttributes(
7227+
attribute_patterns=r["attributePatterns"],
7228+
features=FilterableAttributeFeatures(**r["features"]),
7229+
)
7230+
)
71817231

7182-
def update_filterable_attributes(self, body: list[str], *, compress: bool = False) -> TaskInfo:
7232+
return filterable_attributes
7233+
7234+
def update_filterable_attributes(
7235+
self, body: list[str] | list[FilterableAttributes], *, compress: bool = False
7236+
) -> TaskInfo:
71837237
"""Update filterable attributes of the index.
71847238
71857239
Args:
@@ -7199,8 +7253,16 @@ def update_filterable_attributes(self, body: list[str], *, compress: bool = Fals
71997253
>>> index = client.index("movies")
72007254
>>> index.update_filterable_attributes(["genre", "director"])
72017255
"""
7256+
payload: list[str | JsonDict] = []
7257+
7258+
for b in body:
7259+
if isinstance(b, FilterableAttributes):
7260+
payload.append(b.model_dump(by_alias=True))
7261+
else:
7262+
payload.append(b)
7263+
72027264
response = self._http_requests.put(
7203-
f"{self._settings_url}/filterable-attributes", body, compress=compress
7265+
f"{self._settings_url}/filterable-attributes", payload, compress=compress
72047266
)
72057267

72067268
return TaskInfo(**response.json())
@@ -8326,6 +8388,7 @@ def _process_search_parameters(
83268388
hybrid: Hybrid | None = None,
83278389
locales: list[str] | None = None,
83288390
retrieve_vectors: bool | None = None,
8391+
exhaustive_facet_count: bool | None = None,
83298392
) -> JsonDict:
83308393
if attributes_to_retrieve is None:
83318394
attributes_to_retrieve = ["*"]
@@ -8377,6 +8440,9 @@ def _process_search_parameters(
83778440
if retrieve_vectors is not None:
83788441
body["retrieveVectors"] = retrieve_vectors
83798442

8443+
if exhaustive_facet_count is not None:
8444+
body["exhaustivefacetCount"] = exhaustive_facet_count
8445+
83808446
return body
83818447

83828448

meilisearch_python_sdk/models/batch.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class Stats(CamelBase):
2626
status: Status
2727
batch_types: JsonDict | None = Field(None, alias="types")
2828
index_uids: JsonDict | None = None
29+
progress_trace: JsonDict | None = None
30+
write_channel_congestion: JsonDict | None = None
31+
internal_database_sizes: JsonDict | None = None
2932

3033

3134
class BatchResult(BatchId):

meilisearch_python_sdk/models/settings.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class HuggingFaceEmbedder(CamelBase):
6868
distribution: Distribution | None = None
6969
dimensions: int | None = None
7070
binary_quantized: bool | None = None
71+
pooling: Literal["useModel", "forceMean", "forceCls"] | None = None
7172

7273

7374
class OllamaEmbedder(CamelBase):
@@ -122,11 +123,26 @@ class LocalizedAttributes(CamelBase):
122123
attribute_patterns: list[str]
123124

124125

126+
class Filter(CamelBase):
127+
equality: bool
128+
comparison: bool
129+
130+
131+
class FilterableAttributeFeatures(CamelBase):
132+
facet_search: bool
133+
filter: Filter
134+
135+
136+
class FilterableAttributes(CamelBase):
137+
attribute_patterns: list[str]
138+
features: FilterableAttributeFeatures
139+
140+
125141
class MeilisearchSettings(CamelBase):
126142
synonyms: JsonDict | None = None
127143
stop_words: list[str] | None = None
128144
ranking_rules: list[str] | None = None
129-
filterable_attributes: list[str] | None = None
145+
filterable_attributes: list[str] | list[FilterableAttributes] | None = None
130146
distinct_attribute: str | None = None
131147
searchable_attributes: list[str] | None = None
132148
displayed_attributes: list[str] | None = None

tests/test_async_index.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from meilisearch_python_sdk.models.settings import (
88
Embedders,
99
Faceting,
10+
Filter,
11+
FilterableAttributeFeatures,
12+
FilterableAttributes,
1013
LocalizedAttributes,
1114
MinWordSizeForTypos,
1215
OpenAiEmbedder,
@@ -63,11 +66,6 @@ def new_synonyms():
6366
return {"hp": ["harry potter"]}
6467

6568

66-
@pytest.fixture
67-
def filterable_attributes():
68-
return ["release_date", "title"]
69-
70-
7169
@pytest.fixture
7270
def default_pagination():
7371
return Pagination(max_total_hits=1000)
@@ -577,6 +575,20 @@ async def test_get_filterable_attributes(async_empty_index):
577575

578576

579577
@pytest.mark.parametrize("compress", (True, False))
578+
@pytest.mark.parametrize(
579+
"filterable_attributes",
580+
(
581+
["release_date", "title"],
582+
[
583+
FilterableAttributes(
584+
attribute_patterns=["release_date", "title"],
585+
features=FilterableAttributeFeatures(
586+
facet_search=True, filter=Filter(equality=True, comparison=False)
587+
),
588+
),
589+
],
590+
),
591+
)
580592
async def test_update_filterable_attributes(compress, async_empty_index, filterable_attributes):
581593
index = await async_empty_index()
582594
response = await index.update_filterable_attributes(filterable_attributes, compress=compress)
@@ -585,6 +597,20 @@ async def test_update_filterable_attributes(compress, async_empty_index, filtera
585597
assert sorted(response) == filterable_attributes
586598

587599

600+
@pytest.mark.parametrize(
601+
"filterable_attributes",
602+
(
603+
["release_date", "title"],
604+
[
605+
FilterableAttributes(
606+
attribute_patterns=["release_date", "title"],
607+
features=FilterableAttributeFeatures(
608+
facet_search=True, filter=Filter(equality=True, comparison=False)
609+
),
610+
),
611+
],
612+
),
613+
)
588614
async def test_reset_filterable_attributes(async_empty_index, filterable_attributes):
589615
index = await async_empty_index()
590616
response = await index.update_filterable_attributes(filterable_attributes)

tests/test_async_search.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,20 @@ async def test_facet_search_locales(async_index_with_documents):
576576
assert response.facet_hits[0].count == 1
577577

578578

579+
async def test_facet_search_exhaustive_facet_count(async_index_with_documents):
580+
index = await async_index_with_documents()
581+
update = await index.update_filterable_attributes(["genre"])
582+
await async_wait_for_task(index.http_client, update.task_uid)
583+
response = await index.facet_search(
584+
"How to Train Your Dragon",
585+
facet_name="genre",
586+
facet_query="cartoon",
587+
exhaustive_facet_count=True,
588+
)
589+
590+
assert response.facet_hits[0].value == "cartoon"
591+
592+
579593
@pytest.mark.parametrize("ranking_score_threshold", (-0.1, 1.1))
580594
async def test_search_invalid_ranking_score_threshold(
581595
ranking_score_threshold, async_index_with_documents

0 commit comments

Comments
 (0)