feat: Add support for Dynamic Search Rules (v1.41)#1228
feat: Add support for Dynamic Search Rules (v1.41)#1228dotsystemsdevs wants to merge 5 commits intomeilisearch:mainfrom
Conversation
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 meilisearch#1227
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds Meilisearch dynamic search rules: a config path constant, four new Index methods (list, get, upsert, delete), end-to-end tests for lifecycle and isolation, and appended documentation code samples. All changes are additive; no existing public APIs were removed or altered. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client / Index
participant Server as Meilisearch Server
participant Tasks as Task Service
Client->>Server: POST /indexes/:uid/dynamic-search-rules/fetch (parameters)
Server-->>Client: 200 { results / meta }
Client->>Server: PATCH /indexes/:uid/dynamic-search-rules/:rule_uid (body)
Server-->>Client: 202 { "taskUid": "123" }
Client->>Tasks: GET /tasks/123 (poll)
Tasks-->>Client: 200 { "status": "succeeded" }
Client->>Server: GET /indexes/:uid/dynamic-search-rules/:rule_uid
Server-->>Client: 200 { "uid": "promote-new", ... }
Client->>Server: DELETE /indexes/:uid/dynamic-search-rules/:rule_uid
Server-->>Client: 202 { "taskUid": "124" }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.code-samples.meilisearch.yaml:
- Around line 632-643: The sample payload for patch_dynamic_search_rule_1 uses
snake_case and the wrong shape—update the payload sent via
client.index('movies').upsert_dynamic_search_rule to use camelCase keys and the
API's expected structure (e.g., use "conditions" as an array of condition
objects with fields like "scope", "operator"/"isEmpty" as appropriate, and
"actions" with "documentIds" instead of "document_ids" and "matchCondition"
instead of "match_condition"); ensure all outgoing keys follow camelCase and
match the Meilisearch dynamic search rules spec so the SDK sends the correct
body shape to upsert_dynamic_search_rule.
In `@meilisearch/index.py`:
- Around line 1365-1391: In upsert_dynamic_search_rule, the request path is
incorrectly built as index-scoped; update the HTTP call to use the top-level
dynamic search rules route by constructing the path from
self.config.paths.dynamic_search_rules and the uid only (remove the index
prefix), keep using self.http.patch and return TaskInfo(**task) as before; refer
to the upsert_dynamic_search_rule method and the
self.config.paths.dynamic_search_rules symbol to locate the change.
In `@tests/index/test_index_dynamic_search_rules_meilisearch.py`:
- Around line 11-15: The current test test_list_dynamic_search_rules uses a
tautological assertion; change it to assert meaningful properties of the
response from test_index.list_dynamic_search_rules — for example, verify the
response is a dict, assert that the "results" key exists (or if API may omit it,
assert at least one of the expected keys like "results" or "meta" exists), and
if "results" is present assert it's a list (and optionally that its items have
expected shape). Locate the assertion in test_list_dynamic_search_rules and
replace the tautology with these concrete checks referencing
test_index.list_dynamic_search_rules and the "results" key.
- Around line 94-99: Replace the try/except block around
test_index.get_dynamic_search_rule(rule_uid) with a pytest.raises context
manager to assert the call raises the expected error; specifically, wrap the
get_dynamic_search_rule(rule_uid) invocation in pytest.raises(...) instead of
catching Exception and using assert False, and if a specific error type exists
in the project (e.g., a NotFound or MeiliSearchApiError), use that specific
exception class in pytest.raises to avoid a broad Exception catch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ba9822b4-c2c3-4eea-b929-ba8720fb3241
📒 Files selected for processing (4)
.code-samples.meilisearch.yamlmeilisearch/config.pymeilisearch/index.pytests/index/test_index_dynamic_search_rules_meilisearch.py
- index.py: Fix upsert_dynamic_search_rule path to use top-level route
/dynamic-search-rules/{uid} instead of index-scoped path
- .code-samples.meilisearch.yaml: Fix patch sample to use camelCase keys
(documentIds, matchCondition) and correct conditions array structure
with scope/operator/value fields per v1.41 API spec
- tests: Replace tautological assertion in test_list with meaningful
checks (isinstance dict, results/meta key, results is list)
- tests: Replace try/except with pytest.raises(MeilisearchApiError)
in test_delete verification; add MeilisearchApiError import
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
tests/index/test_index_dynamic_search_rules_meilisearch.py (2)
96-96: Comment and assertion are slightly inconsistent.Line 96 says “raise error or return empty,” but the test strictly enforces an exception. Update the comment to reflect the asserted behavior only.
✏️ Tiny wording fix
- # Verify it's deleted - should raise error or return empty + # Verify it's deleted - should raise MeilisearchApiError🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/index/test_index_dynamic_search_rules_meilisearch.py` at line 96, Update the inline comment that currently reads “Verify it's deleted - should raise error or return empty” to reflect the asserted behavior in the test (that deletion must raise an exception). Modify the comment in tests/index/test_index_dynamic_search_rules_meilisearch.py to state something like “Verify it's deleted - should raise an exception” so the comment matches the strict assertion in this test case.
5-10: Wait for index deletion task in fixture teardown to reduce flakiness.On Line 9, cleanup triggers async deletion but doesn’t wait for completion. That can leak state across tests in CI.
🔧 Suggested cleanup hardening
`@pytest.fixture` def test_index(client_with_index): index = client_with_index() - yield index - index.delete() + try: + yield index + finally: + delete_task = index.delete() + index.wait_for_task(delete_task.task_uid)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/index/test_index_dynamic_search_rules_meilisearch.py` around lines 5 - 10, The fixture teardown calls index.delete() but doesn't wait for the asynchronous delete to finish, causing flakiness; update the test_index fixture to capture the deletion task returned by index.delete() and then wait for its completion (e.g., use the Meilisearch client or index API like wait_for_task / wait_for_pending_update with the returned task id/uid) before returning from teardown, and add a small timeout/retry and ignore-not-found handling so the cleanup is robust; reference the fixture name test_index, the client_with_index() call that creates the index, and the index.delete() call to locate where to add the wait logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/index/test_index_dynamic_search_rules_meilisearch.py`:
- Around line 25-35: The test fixtures use snake_case keys in the rule payload
(e.g., rule_body with "match_condition" and "document_ids") but the v1.41 API
requires camelCase; update every test rule payload (the rule_body dicts used in
these tests) to use "matchCondition" and "documentIds" instead, ensuring the
keys are changed before sending PATCH requests because the SDK does not
transform dict keys and patch() lacks custom serialization.
---
Nitpick comments:
In `@tests/index/test_index_dynamic_search_rules_meilisearch.py`:
- Line 96: Update the inline comment that currently reads “Verify it's deleted -
should raise error or return empty” to reflect the asserted behavior in the test
(that deletion must raise an exception). Modify the comment in
tests/index/test_index_dynamic_search_rules_meilisearch.py to state something
like “Verify it's deleted - should raise an exception” so the comment matches
the strict assertion in this test case.
- Around line 5-10: The fixture teardown calls index.delete() but doesn't wait
for the asynchronous delete to finish, causing flakiness; update the test_index
fixture to capture the deletion task returned by index.delete() and then wait
for its completion (e.g., use the Meilisearch client or index API like
wait_for_task / wait_for_pending_update with the returned task id/uid) before
returning from teardown, and add a small timeout/retry and ignore-not-found
handling so the cleanup is robust; reference the fixture name test_index, the
client_with_index() call that creates the index, and the index.delete() call to
locate where to add the wait logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4197b37a-4c51-480c-9c0c-08141bcc8d2f
📒 Files selected for processing (3)
.code-samples.meilisearch.yamlmeilisearch/index.pytests/index/test_index_dynamic_search_rules_meilisearch.py
✅ Files skipped from review due to trivial changes (1)
- .code-samples.meilisearch.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
- meilisearch/index.py
Replace match_condition → matchCondition and document_ids → documentIds in all test rule_body fixtures (lines 25-35, 49-59, 74-84)
Replace single 'condition' string with 'conditions' array containing scope/operator/value objects. Move matchCondition inside actions object. All payloads now match the v1.41 Dynamic Search Rules API spec.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/index/test_index_dynamic_search_rules_meilisearch.py (1)
12-18: Tighten list-response assertions.
assert "results" in response or "meta" in responseis still broad and can mask partial payloads. Prefer asserting the expected list contract directly (e.g.,"results"exists and is a list, and optionally"meta"shape when present).Proposed assertion tightening
def test_list_dynamic_search_rules(test_index): """Test listing dynamic search rules""" response = test_index.list_dynamic_search_rules() assert isinstance(response, dict) - assert "results" in response or "meta" in response - if "results" in response: - assert isinstance(response["results"], list) + assert "results" in response + assert isinstance(response["results"], list) + if "meta" in response: + assert isinstance(response["meta"], dict)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/index/test_index_dynamic_search_rules_meilisearch.py` around lines 12 - 18, Update the test_list_dynamic_search_rules test to assert the concrete list contract instead of the broad or condition: require that response contains the "results" key and that response["results"] is a list (use isinstance), and if "meta" is present assert it is a dict and optionally validate expected fields (e.g., "total" or pagination keys) to tighten the payload shape; locate and change the assertions in test_list_dynamic_search_rules that currently check response = test_index.list_dynamic_search_rules() and the subsequent "results"/"meta" or-assertion to these stricter checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/index/test_index_dynamic_search_rules_meilisearch.py`:
- Around line 50-77: Add a test that verifies multi-index isolation for dynamic
search rules by creating two separate TestIndex instances and upserting the same
rule_uid into each using upsert_dynamic_search_rule, then waiting for tasks and
calling get_dynamic_search_rule on each index to assert each index returns its
own rule (uid and body) and that they do not overwrite each other; reference
upsert_dynamic_search_rule, get_dynamic_search_rule, and
delete_dynamic_search_rule to locate the related code paths (the bug is that
upsert builds a non-index-scoped path), so ensure the test reproduces the
endpoint-scoping mismatch by asserting isolation and failing if upsert affects
both indexes.
---
Nitpick comments:
In `@tests/index/test_index_dynamic_search_rules_meilisearch.py`:
- Around line 12-18: Update the test_list_dynamic_search_rules test to assert
the concrete list contract instead of the broad or condition: require that
response contains the "results" key and that response["results"] is a list (use
isinstance), and if "meta" is present assert it is a dict and optionally
validate expected fields (e.g., "total" or pagination keys) to tighten the
payload shape; locate and change the assertions in
test_list_dynamic_search_rules that currently check response =
test_index.list_dynamic_search_rules() and the subsequent "results"/"meta"
or-assertion to these stricter checks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 05993f5d-eb18-4494-88c2-5db43b9ec91b
📒 Files selected for processing (1)
tests/index/test_index_dynamic_search_rules_meilisearch.py
| def test_upsert_dynamic_search_rule(test_index): | ||
| """Test creating or updating a dynamic search rule""" | ||
| rule_uid = "test-rule-2" | ||
| rule_body = { | ||
| "conditions": [ | ||
| { | ||
| "scope": ["title"], | ||
| "operator": "contains", | ||
| "value": "hello" | ||
| } | ||
| ], | ||
| "actions": [ | ||
| { | ||
| "action": "promote", | ||
| "documentIds": ["2"], | ||
| "matchCondition": "all", | ||
| "position": 1 | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| response = test_index.upsert_dynamic_search_rule(rule_uid, rule_body) | ||
| assert response.task_uid is not None | ||
|
|
||
| test_index.wait_for_task(response.task_uid) | ||
|
|
||
| retrieved = test_index.get_dynamic_search_rule(rule_uid) | ||
| assert retrieved.get("uid") == rule_uid |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify path construction consistency across dynamic search rule methods.
rg -n -C3 'def (upsert_dynamic_search_rule|get_dynamic_search_rule|delete_dynamic_search_rule)\(' meilisearch/index.py
rg -n -C2 'dynamic_search_rules.*uid' meilisearch/index.pyRepository: meilisearch/meilisearch-python
Length of output: 1319
Add a multi-index isolation test to catch endpoint scoping regressions in upsert_dynamic_search_rule.
The current tests use only a single index, which masks a critical bug: upsert_dynamic_search_rule builds a non-index-scoped path (meilisearch/index.py:1387) while get_dynamic_search_rule (line 1362) and delete_dynamic_search_rule (line 1413) both include the index UID. This path inconsistency means upsert operations affect all indexes instead of being isolated per-index. Add a test that upserts the same rule_uid in two separate indexes and verifies that retrieval returns the correct rule per index.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/index/test_index_dynamic_search_rules_meilisearch.py` around lines 50 -
77, Add a test that verifies multi-index isolation for dynamic search rules by
creating two separate TestIndex instances and upserting the same rule_uid into
each using upsert_dynamic_search_rule, then waiting for tasks and calling
get_dynamic_search_rule on each index to assert each index returns its own rule
(uid and body) and that they do not overwrite each other; reference
upsert_dynamic_search_rule, get_dynamic_search_rule, and
delete_dynamic_search_rule to locate the related code paths (the bug is that
upsert builds a non-index-scoped path), so ensure the test reproduces the
endpoint-scoping mismatch by asserting isolation and failing if upsert affects
both indexes.
…test
- upsert_dynamic_search_rule: restore index-scoped path for consistency
with list/get/delete (all use {index}/{uid}/{dynamic_search_rules}/...)
- Add test_upsert_dynamic_search_rule_index_isolation: creates the same
rule_uid on two separate indexes and verifies rules are independent
|
I’ve only looked at this long enough to have a strong suspicion it is 100% LLM generated without being reviewed before submitting the PR. |
Summary
Implements support for Meilisearch v1.41 Dynamic Search Rules API as requested in issue #1227.
Changes
1. Configuration (config.py)
dynamic_search_rules = 'dynamic-search-rules'path to Config.Paths2. Index Methods (index.py)
Added 4 new methods to the Index class:
list_dynamic_search_rules(parameters) - 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}
3. Tests (test_index_dynamic_search_rules_meilisearch.py)
Added comprehensive test cases covering:
4. Code Samples (.code-samples.meilisearch.yaml)
Added examples for:
list_dynamic_search_rules_1get_dynamic_search_rule_1patch_dynamic_search_rule_1delete_dynamic_search_rule_1Checklist
Summary by CodeRabbit
New Features
Documentation
Tests