Skip to content

feat: Add support for Dynamic Search Rules (v1.41)#1228

Open
dotsystemsdevs wants to merge 5 commits intomeilisearch:mainfrom
dotsystemsdevs:main
Open

feat: Add support for Dynamic Search Rules (v1.41)#1228
dotsystemsdevs wants to merge 5 commits intomeilisearch:mainfrom
dotsystemsdevs:main

Conversation

@dotsystemsdevs
Copy link
Copy Markdown

@dotsystemsdevs dotsystemsdevs commented Apr 8, 2026

Summary

Implements support for Meilisearch v1.41 Dynamic Search Rules API as requested in issue #1227.

Changes

1. Configuration (config.py)

  • Added dynamic_search_rules = 'dynamic-search-rules' path to Config.Paths

2. Index Methods (index.py)

Added 4 new methods to the Index class:

  • list_dynamic_search_rules(parameters) - POST /indexes/{uid}/dynamic-search-rules

    • Lists all dynamic search rules with pagination support
  • get_dynamic_search_rule(uid) - GET /indexes/{uid}/dynamic-search-rules/{uid}

    • Retrieves a specific rule by uid
  • upsert_dynamic_search_rule(uid, body) - PATCH /indexes/{uid}/dynamic-search-rules/{uid}

    • Creates or updates a rule
  • delete_dynamic_search_rule(uid) - DELETE /indexes/{uid}/dynamic-search-rules/{uid}

    • Deletes a rule by uid

3. Tests (test_index_dynamic_search_rules_meilisearch.py)

Added comprehensive test cases covering:

  • Listing rules
  • Getting a specific rule
  • Creating/updating rules
  • Deleting rules

4. Code Samples (.code-samples.meilisearch.yaml)

Added examples for:

  • list_dynamic_search_rules_1
  • get_dynamic_search_rule_1
  • patch_dynamic_search_rule_1
  • delete_dynamic_search_rule_1

Checklist

Summary by CodeRabbit

  • New Features

    • Added dynamic search rules management for indexes: list rules, retrieve by UID, create/update rules with conditions and actions (e.g., promote specific documents), and delete rules.
  • Documentation

    • Added code samples demonstrating the full dynamic search rules lifecycle on the movies index, including condition-based promote examples.
  • Tests

    • Added end-to-end tests covering listing, upserting, retrieving, deleting, and index-isolation scenarios for dynamic search rules.

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>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 43d7f87f-d7b6-414f-bbd7-125bbac84d57

📥 Commits

Reviewing files that changed from the base of the PR and between c5bd2be and c056a2c.

📒 Files selected for processing (2)
  • meilisearch/index.py
  • tests/index/test_index_dynamic_search_rules_meilisearch.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • meilisearch/index.py
  • tests/index/test_index_dynamic_search_rules_meilisearch.py

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Configuration
meilisearch/config.py
Added Config.Paths.dynamic_search_rules = "dynamic-search-rules" constant.
Index Methods
meilisearch/index.py
Added Index methods: list_dynamic_search_rules(parameters=None), get_dynamic_search_rule(uid), upsert_dynamic_search_rule(uid, body) (returns TaskInfo), and delete_dynamic_search_rule(uid) (returns TaskInfo).
Test Coverage
tests/index/test_index_dynamic_search_rules_meilisearch.py
New pytest module with test_index fixture and tests for listing, getting, upserting, deleting dynamic search rules, task polling, and index-isolation scenarios.
Documentation
.code-samples.meilisearch.yaml
Appended code samples demonstrating listing, fetching, upserting (with conditions and actions) and deleting dynamic search rules on the movies index.

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" }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nudged a path, a rule, a test,
Woke movies up from list to best.
PATCH then poll, a tiny quest,
Docs and tests tucked in my nest. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main addition: support for Dynamic Search Rules, with version reference (v1.41).
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between c9bdb42 and 1b56432.

📒 Files selected for processing (4)
  • .code-samples.meilisearch.yaml
  • meilisearch/config.py
  • meilisearch/index.py
  • tests/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
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1b56432 and 74dd9eb.

📒 Files selected for processing (3)
  • .code-samples.meilisearch.yaml
  • meilisearch/index.py
  • tests/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

diana added 2 commits April 8, 2026 12:07
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.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 response is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6f918cb and c5bd2be.

📒 Files selected for processing (1)
  • tests/index/test_index_dynamic_search_rules_meilisearch.py

Comment on lines +50 to +77
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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.py

Repository: 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
@sanders41
Copy link
Copy Markdown
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants