Skip to content

Commit 1b56432

Browse files
dianaclaude
andcommitted
feat: Add support for Dynamic Search Rules (v1.41)
Add support for Meilisearch v1.41 Dynamic Search Rules API: - Add 'dynamic_search_rules' path to Config.Paths - Implement 4 new index methods: - list_dynamic_search_rules(): POST /indexes/{uid}/dynamic-search-rules - get_dynamic_search_rule(uid): GET /indexes/{uid}/dynamic-search-rules/{uid} - upsert_dynamic_search_rule(uid, body): PATCH /indexes/{uid}/dynamic-search-rules/{uid} - delete_dynamic_search_rule(uid): DELETE /indexes/{uid}/dynamic-search-rules/{uid} - Add comprehensive test cases for all methods - Add code examples to .code-samples.meilisearch.yaml - Include proper docstrings with parameters, returns, and error documentation Closes #1227 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent c9bdb42 commit 1b56432

4 files changed

Lines changed: 228 additions & 0 deletions

File tree

.code-samples.meilisearch.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,3 +622,25 @@ update_embedders_1: |-
622622
})
623623
reset_embedders_1: |-
624624
client.index('INDEX_NAME').reset_embedders()
625+
626+
list_dynamic_search_rules_1: |-
627+
client.index('movies').list_dynamic_search_rules()
628+
629+
get_dynamic_search_rule_1: |-
630+
client.index('movies').get_dynamic_search_rule('promote-new')
631+
632+
patch_dynamic_search_rule_1: |-
633+
client.index('movies').upsert_dynamic_search_rule('promote-new', {
634+
'condition': "query = 'new'",
635+
'match_condition': 'all',
636+
'actions': [
637+
{
638+
'action': 'promote',
639+
'document_ids': ['1', '2'],
640+
'position': 1
641+
}
642+
]
643+
})
644+
645+
delete_dynamic_search_rule_1: |-
646+
client.index('movies').delete_dynamic_search_rule('promote-new')

meilisearch/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class Paths:
5151
experimental_features = "experimental-features"
5252
webhooks = "webhooks"
5353
export = "export"
54+
dynamic_search_rules = "dynamic-search-rules"
5455

5556
def __init__(
5657
self,

meilisearch/index.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,112 @@ def reset_settings(self, *, metadata: Optional[str] = None) -> TaskInfo:
13091309

13101310
return TaskInfo(**task)
13111311

1312+
1313+
# DYNAMIC SEARCH RULES SUB-ROUTES
1314+
1315+
def list_dynamic_search_rules(
1316+
self, parameters: Optional[MutableMapping[str, Any]] = None
1317+
) -> Dict[str, Any]:
1318+
"""List all dynamic search rules of the index.
1319+
1320+
Parameters
1321+
----------
1322+
parameters (optional):
1323+
parameters accepted by the list dynamic search rules route, including pagination and filtering options.
1324+
1325+
Returns
1326+
-------
1327+
rules: dict
1328+
Dictionary containing the list of dynamic search rules and pagination info.
1329+
1330+
Raises
1331+
------
1332+
MeilisearchApiError
1333+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
1334+
"""
1335+
if parameters is None:
1336+
parameters = {}
1337+
1338+
return self.http.post(
1339+
f"{self.config.paths.index}/{self.uid}/{self.config.paths.dynamic_search_rules}/fetch",
1340+
body=parameters,
1341+
)
1342+
1343+
def get_dynamic_search_rule(self, uid: str) -> Dict[str, Any]:
1344+
"""Get a single dynamic search rule by uid.
1345+
1346+
Parameters
1347+
----------
1348+
uid: str
1349+
Unique identifier of the dynamic search rule.
1350+
1351+
Returns
1352+
-------
1353+
rule: dict
1354+
Dictionary containing the dynamic search rule data.
1355+
1356+
Raises
1357+
------
1358+
MeilisearchApiError
1359+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
1360+
"""
1361+
return self.http.get(
1362+
f"{self.config.paths.index}/{self.uid}/{self.config.paths.dynamic_search_rules}/{uid}"
1363+
)
1364+
1365+
def upsert_dynamic_search_rule(self, uid: str, body: Dict[str, Any]) -> TaskInfo:
1366+
"""Create or update a dynamic search rule.
1367+
1368+
Parameters
1369+
----------
1370+
uid: str
1371+
Unique identifier of the dynamic search rule.
1372+
body: dict
1373+
Dictionary containing the dynamic search rule configuration.
1374+
1375+
Returns
1376+
-------
1377+
task_info:
1378+
TaskInfo instance containing information about a task to track the progress of an asynchronous process.
1379+
https://www.meilisearch.com/docs/reference/api/tasks#get-one-task
1380+
1381+
Raises
1382+
------
1383+
MeilisearchApiError
1384+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
1385+
"""
1386+
task = self.http.patch(
1387+
f"{self.config.paths.index}/{self.uid}/{self.config.paths.dynamic_search_rules}/{uid}",
1388+
body,
1389+
)
1390+
1391+
return TaskInfo(**task)
1392+
1393+
def delete_dynamic_search_rule(self, uid: str) -> TaskInfo:
1394+
"""Delete a dynamic search rule by uid.
1395+
1396+
Parameters
1397+
----------
1398+
uid: str
1399+
Unique identifier of the dynamic search rule to delete.
1400+
1401+
Returns
1402+
-------
1403+
task_info:
1404+
TaskInfo instance containing information about a task to track the progress of an asynchronous process.
1405+
https://www.meilisearch.com/docs/reference/api/tasks#get-one-task
1406+
1407+
Raises
1408+
------
1409+
MeilisearchApiError
1410+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
1411+
"""
1412+
task = self.http.delete(
1413+
f"{self.config.paths.index}/{self.uid}/{self.config.paths.dynamic_search_rules}/{uid}"
1414+
)
1415+
1416+
return TaskInfo(**task)
1417+
13121418
# RANKING RULES SUB-ROUTES
13131419

13141420
def get_ranking_rules(self) -> List[str]:
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def test_index(client_with_index):
6+
index = client_with_index()
7+
yield index
8+
index.delete()
9+
10+
11+
def test_list_dynamic_search_rules(test_index):
12+
"""Test listing dynamic search rules"""
13+
response = test_index.list_dynamic_search_rules()
14+
assert isinstance(response, dict)
15+
assert "results" in response or "results" not in response # May be empty
16+
17+
18+
def test_get_dynamic_search_rule(test_index):
19+
"""Test getting a single dynamic search rule"""
20+
# Create a rule first
21+
rule_uid = "test-rule-1"
22+
rule_body = {
23+
"condition": "query = 'new'",
24+
"match_condition": "all",
25+
"actions": [
26+
{
27+
"action": "promote",
28+
"document_ids": ["1"],
29+
"position": 1
30+
}
31+
]
32+
}
33+
34+
create_response = test_index.upsert_dynamic_search_rule(rule_uid, rule_body)
35+
test_index.wait_for_task(create_response.task_uid)
36+
37+
# Now retrieve it
38+
response = test_index.get_dynamic_search_rule(rule_uid)
39+
assert isinstance(response, dict)
40+
assert response.get("uid") == rule_uid
41+
42+
43+
def test_upsert_dynamic_search_rule(test_index):
44+
"""Test creating or updating a dynamic search rule"""
45+
rule_uid = "test-rule-2"
46+
rule_body = {
47+
"condition": "query = 'hello'",
48+
"match_condition": "all",
49+
"actions": [
50+
{
51+
"action": "promote",
52+
"document_ids": ["2"],
53+
"position": 1
54+
}
55+
]
56+
}
57+
58+
response = test_index.upsert_dynamic_search_rule(rule_uid, rule_body)
59+
assert response.task_uid is not None
60+
61+
test_index.wait_for_task(response.task_uid)
62+
63+
# Verify it was created
64+
retrieved = test_index.get_dynamic_search_rule(rule_uid)
65+
assert retrieved.get("uid") == rule_uid
66+
67+
68+
def test_delete_dynamic_search_rule(test_index):
69+
"""Test deleting a dynamic search rule"""
70+
rule_uid = "test-rule-3"
71+
rule_body = {
72+
"condition": "query = 'delete-me'",
73+
"match_condition": "all",
74+
"actions": [
75+
{
76+
"action": "promote",
77+
"document_ids": ["3"],
78+
"position": 1
79+
}
80+
]
81+
}
82+
83+
# Create rule
84+
create_response = test_index.upsert_dynamic_search_rule(rule_uid, rule_body)
85+
test_index.wait_for_task(create_response.task_uid)
86+
87+
# Delete it
88+
delete_response = test_index.delete_dynamic_search_rule(rule_uid)
89+
assert delete_response.task_uid is not None
90+
91+
test_index.wait_for_task(delete_response.task_uid)
92+
93+
# Verify it's deleted - should raise error or return empty
94+
try:
95+
test_index.get_dynamic_search_rule(rule_uid)
96+
assert False, "Rule should have been deleted"
97+
except Exception:
98+
# Expected - rule doesn't exist
99+
pass

0 commit comments

Comments
 (0)