Skip to content

Conversation

@jawad-khan
Copy link
Contributor

@jawad-khan jawad-khan commented Nov 26, 2025

Pull Request

Added support for the Export API as described in the official Meilisearch docs.

  • Add the new methods to interact with the Meilisearch API
  • Add new tests cases
  • Write an example code in .code-samples.meilisearch.yaml under the export_post_1 key

Related issue

Fixes # 1129

PR checklist

Please check if your PR fulfills the following requirements:

  • Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)?
  • Have you read the contributing guidelines?
  • Have you made sure that the title is accurate and descriptive of the changes?

Thank you so much for contributing to Meilisearch!

Summary by CodeRabbit

  • New Features

    • Added an export API to initiate cross-instance exports with optional API key, payload size, and index filters.
  • Tests

    • End-to-end tests validating full and filtered exports between two Meilisearch instances.
  • Chores

    • Added optional secondary Meilisearch service for local/integration setups and updated test setup/teardown to operate across both instances.
  • Documentation

    • Added a code sample demonstrating cross-instance export usage.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Warning

Rate limit exceeded

@jawad-khan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 14 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 88912c3 and 9ea1c39.

📒 Files selected for processing (2)
  • .github/workflows/tests.yml
  • tests/conftest.py
📝 Walkthrough

Walkthrough

Adds an exports API path and Client.export(...), provisions a second Meilisearch instance and client (client2) for tests, adds end-to-end export tests against the secondary instance, and adds a code sample demonstrating an export request.

Changes

Cohort / File(s) Summary
Client API & Config
meilisearch/client.py, meilisearch/config.py
Add Client.export(url, api_key=None, payload_size=None, indexes=None) -> TaskInfo that builds payload and POSTs to the exports endpoint; add Config.Paths.exports = "export".
Compose & CI
docker-compose.yml, .github/workflows/tests.yml
Add secondary Meilisearch service meilisearch2 (container meili2, host port 7701) and expose MEILISEARCH_URL_2; CI starts a second Meilisearch container and sets MEILISEARCH_URL_2 for tests.
Tests — fixtures & constants
tests/common.py, tests/conftest.py
Add BASE_URL_2 (from MEILISEARCH_URL_2), new client2 fixture, helper _clear_indexes(meilisearch_client), and update clear_indexes and clear_all_tasks to operate on both instances when configured; update vector-search toggles for both URLs.
Tests — export scenarios
tests/client/test_client_exports.py
Add test_export_creation and test_export_creation_with_index_filter plus assert_exported_count(...); tests create exports and validate exported index UID, primary key, and document counts on the secondary server (guarded by MEILISEARCH_URL_2).
Code samples
.code-samples.meilisearch.yaml
Add export_post_1 sample under post_dump_1 demonstrating an export call with remote URL, API key, payload size, and indexes map (movies*, books*).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Test as Test runner
    participant Client as Client.export()
    participant Source as Meilisearch_A
    participant Target as Meilisearch_B

    Note over Test,Client: Initiate export from tests to secondary server
    Test->>Client: call export(url, api_key, payload_size, indexes)
    activate Client
    Client->>Client: build payload (url + optional fields)
    Client->>Source: POST /exports (payload)
    activate Source
    Source-->>Client: TaskInfo (task created)
    deactivate Source
    Client-->>Test: return TaskInfo
    deactivate Client

    Note right of Test: Poll task until completion

    Test->>Target: Query indexes / index details
    activate Target
    Target-->>Test: index metadata & document counts
    deactivate Target

    Note over Test,Target: Verify UID, primary key, document count match source
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • Strift

Poem

🐇 I hopped a payload across the stream,

Keys and indexes bundled in a dream.
Two servers woke and shared their store,
Tests counted carrots — one, then more.
A tiny hop — export galore.

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add support for export api' clearly and concisely describes the main change in the pull request—implementing support for the Meilisearch Export API.

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
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: 3

🧹 Nitpick comments (1)
tests/client/test_client_exports.py (1)

13-23: Consider using consistent property access.

The test logic is correct and appropriately verifies the export functionality. However, there's an inconsistency: line 22 uses index.get_primary_key() (method call) while line 21 uses index2.primary_key (property access). Both work, but consistency would improve readability.

Consider using the property consistently:

     index2 = client2.get_index(index.uid)
     assert index2.uid == index.uid
-    assert index2.primary_key == index.get_primary_key()
+    assert index2.primary_key == index.primary_key
     assert index2.get_documents().total == index.get_documents().total
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23debbd and 09a8077.

📒 Files selected for processing (7)
  • .code-samples.meilisearch.yaml (1 hunks)
  • docker-compose.yml (2 hunks)
  • meilisearch/client.py (1 hunks)
  • meilisearch/config.py (1 hunks)
  • tests/client/test_client_exports.py (1 hunks)
  • tests/common.py (1 hunks)
  • tests/conftest.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
meilisearch/client.py (2)
meilisearch/models/task.py (1)
  • TaskInfo (77-106)
meilisearch/_httprequests.py (1)
  • post (98-108)
tests/client/test_client_exports.py (3)
tests/conftest.py (2)
  • client (16-17)
  • client2 (21-22)
meilisearch/client.py (5)
  • index (227-243)
  • export (630-679)
  • wait_for_task (801-828)
  • get_index (185-204)
  • get_indexes (128-159)
meilisearch/index.py (2)
  • get_primary_key (145-153)
  • get_documents (387-425)
🪛 GitHub Actions: Tests
tests/conftest.py

[error] 1-1: isort check failed: Imports are incorrectly sorted and/or formatted.

🔇 Additional comments (9)
meilisearch/config.py (1)

51-51: LGTM!

The new exports path constant follows the established pattern and correctly maps to the "export" endpoint.

docker-compose.yml (2)

9-15: LGTM!

The integration of the second Meilisearch instance into the package service is correctly configured with the environment variable and dependency links.


27-34: LGTM!

The second Meilisearch service configuration mirrors the primary instance appropriately for test purposes. The port mapping 7701:7700 allows external access for testing.

tests/common.py (1)

5-5: LGTM!

The BASE_URL_2 constant follows the same pattern as BASE_URL and correctly defaults to port 7701, matching the docker-compose configuration.

tests/conftest.py (3)

20-32: LGTM!

The client2 fixture and _clear_indexes helper function are well-structured and follow the existing patterns in the test suite.


35-44: LGTM!

The conditional cleanup logic for the second client is appropriate—it only attempts cleanup when MEILISEARCH_URL_2 is configured, preventing errors when the second server isn't available.


63-70: LGTM!

The clear_all_tasks fixture correctly handles cleanup for both clients when the second server is configured.

tests/client/test_client_exports.py (2)

7-10: LGTM!

The skip marker appropriately gates these tests behind the MEILISEARCH_URL_2 environment variable, ensuring they only run when a second server is configured.


26-42: LGTM!

The test correctly verifies that the export respects index filtering, ensuring only the specified index is exported to the second server.

Copy link
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: 0

🧹 Nitpick comments (2)
tests/conftest.py (2)

20-23: Consider making client2 explicitly conditional on second-instance configuration.

Right now client2 is always constructed, even when a second Meilisearch instance is not configured; usage is guarded later by os.getenv("MEILISEARCH_URL_2"), but a misconfigured BASE_URL_2 could still yield confusing failures when a test directly depends on client2. You could optionally make this fixture raise/skip with a clear message when the second instance is not configured (e.g., check MEILISEARCH_URL_2 or common.BASE_URL_2 and call pytest.skip).


25-32: Handle potential pagination in _clear_indexes and reuse existing Index instances.

client.get_indexes() returns a dict with a results list of Index instances built from the paginated /indexes endpoint; the HTTP layer uses Meilisearch’s default pagination (limit=20) when no parameters are provided.(python-sdk.meilisearch.com) If a test run ever generates more than limit indexes, _clear_indexes will only delete the first page despite the “Deletes all the indexes” docstring.

It would be more robust to either:

  • Loop over pages using offset/limit until results is empty, or
  • At least request a higher limit that safely covers expected test indexes.

Since get_indexes already returns Index objects, you can also simplify the loop by calling index.delete() directly instead of recreating them via client.index(index.uid).(python-sdk.meilisearch.com)

Example of a more robust approach (for illustration):

-def _clear_indexes(meilisearch_client):
-    """Deletes all the indexes in the Meilisearch instance."""
-
-    indexes = meilisearch_client.get_indexes()
-    for index in indexes["results"]:
-        task = meilisearch_client.index(index.uid).delete()
-        meilisearch_client.wait_for_task(task.task_uid)
+def _clear_indexes(meilisearch_client):
+    """Deletes all the indexes in the Meilisearch instance (handles pagination)."""
+
+    offset, limit = 0, 1000
+    while True:
+        page = meilisearch_client.get_indexes({"offset": offset, "limit": limit})
+        results = page["results"]
+        if not results:
+            break
+        for index in results:
+            task = index.delete()
+            meilisearch_client.wait_for_task(task.task_uid)
+        offset += limit

Also applies to: 35-36, 40-44

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09a8077 and 6103958.

📒 Files selected for processing (1)
  • tests/conftest.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/conftest.py (3)
meilisearch/client.py (3)
  • index (227-243)
  • wait_for_task (801-828)
  • delete_tasks (782-799)
meilisearch/_httprequests.py (1)
  • delete (142-147)
meilisearch/index.py (2)
  • delete (88-105)
  • wait_for_task (233-260)
🔇 Additional comments (2)
tests/conftest.py (2)

2-4: Stdlib import of os is correctly placed.

os is now grouped with the other stdlib imports and used below for MEILISEARCH_URL_2 checks; no issues from a style or functionality perspective.


63-70: Symmetric task cleanup for client2 looks good.

Extending clear_all_tasks to delete finished tasks on client2 when MEILISEARCH_URL_2 is set keeps task state consistent across both instances and matches the behavior introduced in clear_indexes.

Copy link
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: 0

🧹 Nitpick comments (1)
meilisearch/client.py (1)

630-679: export implementation looks correct; consider tightening indexes doc wording.

The method’s behavior (payload construction, field names, and return type) is consistent with other client methods and the Export API, and I don’t see functional issues here.

One small optional improvement: the docstring for indexes says “A set of objects…”, but the type is Mapping[str, Any]. To avoid confusion with Python set, you might rephrase to something like “A mapping/dict whose keys correspond to patterns matching the indexes you want to export (see Export API docs for structure).”

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6103958 and 97c3633.

📒 Files selected for processing (2)
  • .code-samples.meilisearch.yaml (1 hunks)
  • meilisearch/client.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .code-samples.meilisearch.yaml
🧰 Additional context used
🧬 Code graph analysis (1)
meilisearch/client.py (2)
meilisearch/models/task.py (1)
  • TaskInfo (77-106)
meilisearch/_httprequests.py (1)
  • post (98-108)

@jawad-khan
Copy link
Contributor Author

@Strift not sure whether we should wait for meilisearch/meilisearch#6009 or let it go. Locally I was experiencing the same issue and it took quite of my time but here test cases are passing.

@Strift Strift added the enhancement New feature or request label Nov 27, 2025
@Strift Strift linked an issue Nov 27, 2025 that may be closed by this pull request
3 tasks
@Strift
Copy link
Contributor

Strift commented Nov 27, 2025

Hi @jawad-khan and thanks for this PR 🙏

It's best to hold off until the issue is fixed. Otherwise, we would just be setting ourselves up to get reports in this repository that are unrelated to the SDK 😅

Copy link
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

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97c3633 and 10decc3.

📒 Files selected for processing (7)
  • .code-samples.meilisearch.yaml
  • docker-compose.yml
  • meilisearch/client.py
  • meilisearch/config.py
  • tests/client/test_client_exports.py
  • tests/common.py
  • tests/conftest.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • meilisearch/config.py
  • .code-samples.meilisearch.yaml
  • tests/common.py
  • tests/conftest.py
🧰 Additional context used
🧬 Code graph analysis (1)
meilisearch/client.py (2)
meilisearch/models/task.py (1)
  • TaskInfo (78-107)
meilisearch/_httprequests.py (1)
  • post (98-108)
🔇 Additional comments (3)
docker-compose.yml (3)

634-683: LGTM! Clean implementation following established patterns.

The export method is well-implemented and follows the same pattern as other async operations (create_dump, create_snapshot). The docstring is comprehensive, parameter naming is correct, and the payload construction properly handles optional fields with the correct camelCase API keys.


13-23: Export validation logic is correct.

The test properly validates the export by comparing the UID, primary key, and document count between source and destination indexes. The assertions confirm that the export operation transferred the data correctly to the second Meilisearch instance.


26-29: This review comment cannot be addressed as written due to a critical file mismatch.

The file header indicates docker-compose.yml (lines 26-29) with a meilisearch2 service configuration, but the entire comment body discusses Python test fixtures in tests/client/test_client_exports.py. These are unrelated files and content.

Please clarify which file this review is intended for and provide the correct file path and code snippet so the review can be properly assessed.

Likely an incorrect or invalid review comment.

Copy link
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: 2

🧹 Nitpick comments (2)
tests/conftest.py (2)

20-23: Guard client2 fixture against None URL to prevent potential session-scoped errors.

The client2 fixture is session-scoped and will be instantiated even when MEILISEARCH_URL_2 is not set, resulting in meilisearch.Client(None, ...). While the export tests are skipped, other fixtures like clear_indexes and clear_all_tasks depend on client2 and are autouse=True, meaning they run for every test.

Consider adding a guard or making this fixture conditional:

🔎 Proposed fix
 @fixture(scope="session")
 def client2():
+    if not common.BASE_URL_2:
+        return None
     return meilisearch.Client(common.BASE_URL_2, common.MASTER_KEY)

Then update the conditional checks to handle None:

-    if os.getenv("MEILISEARCH_URL_2"):
+    if client2 is not None:
         _clear_indexes(client2)

275-280: Add guard for BASE_URL_2 in enable_vector_search fixture.

The fixture patches BASE_URL_2 unconditionally. While currently safe (export tests using this fixture are skipped when MEILISEARCH_URL_2 is not set), this is fragile. If the fixture is used by a non-export test, it will fail when the second server is not configured.

🔎 Proposed fix
     requests.patch(
         f"{common.BASE_URL}/experimental-features",
         headers={"Authorization": f"Bearer {common.MASTER_KEY}"},
         json={"vectorStoreSetting": True},
         timeout=10,
     )
-    requests.patch(
-        f"{common.BASE_URL_2}/experimental-features",
-        headers={"Authorization": f"Bearer {common.MASTER_KEY}"},
-        json={"vectorStoreSetting": True},
-        timeout=10,
-    )
+    if common.BASE_URL_2:
+        requests.patch(
+            f"{common.BASE_URL_2}/experimental-features",
+            headers={"Authorization": f"Bearer {common.MASTER_KEY}"},
+            json={"vectorStoreSetting": True},
+            timeout=10,
+        )

Apply the same pattern to the teardown section (lines 288-293).

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10decc3 and 196918e.

📒 Files selected for processing (7)
  • .code-samples.meilisearch.yaml
  • .github/workflows/tests.yml
  • docker-compose.yml
  • meilisearch/client.py
  • tests/client/test_client_exports.py
  • tests/common.py
  • tests/conftest.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • .code-samples.meilisearch.yaml
🧰 Additional context used
🧬 Code graph analysis (2)
meilisearch/client.py (2)
meilisearch/models/task.py (1)
  • TaskInfo (78-107)
meilisearch/_httprequests.py (1)
  • post (98-108)
tests/client/test_client_exports.py (1)
meilisearch/client.py (3)
  • index (231-247)
  • export (634-683)
  • wait_for_task (808-835)
🪛 Ruff (0.14.10)
tests/client/test_client_exports.py

15-15: Unused function argument: enable_vector_search

(ARG001)


30-30: Unused function argument: enable_vector_search

(ARG001)

🔇 Additional comments (5)
meilisearch/client.py (1)

634-683: LGTM!

The export method is well-implemented and follows the existing patterns in the codebase (similar to create_dump and create_snapshot). The payload construction correctly handles optional parameters, and the docstring is comprehensive.

docker-compose.yml (1)

27-34: LGTM!

The second Meilisearch service is properly configured with consistent settings (same image, same master key) as the primary instance. The port mapping (7701:7700) correctly exposes the second instance on a different host port.

tests/common.py (1)

5-5: LGTM!

The BASE_URL_2 intentionally has no default value, which correctly gates the export tests and secondary server cleanup via environment variable checks elsewhere in the test suite.

tests/client/test_client_exports.py (2)

14-26: LGTM!

The test correctly validates the export functionality:

  1. Creates source index with documents
  2. Triggers export to the secondary server
  3. Waits for task completion
  4. Verifies the exported index matches (UID, primary key, document count)

The enable_vector_search fixture is correctly included for its side effects (enabling vector store on both servers), and the pylint: disable=unused-argument comment appropriately silences the linter.


48-60: Well-designed polling helper for eventual consistency.

The assert_exported_count function handles the async nature of document imports with appropriate retry logic (20 attempts × 1 second = 20 second max wait), providing clear failure messages when the timeout is exceeded.

Copy link
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

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7108e9 and 88912c3.

📒 Files selected for processing (1)
  • .github/workflows/tests.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: integration-tests (3.12)
  • GitHub Check: integration-tests (3.9)
  • GitHub Check: integration-tests (3.11)
  • GitHub Check: integration-tests (3.13)
  • GitHub Check: integration-tests (3.10)
🔇 Additional comments (1)
.github/workflows/tests.yml (1)

38-39: LGTM! Environment variable correctly configured.

The missing MEILISEARCH_URL_2 environment variable has been properly added and correctly points to the secondary Meilisearch instance. This ensures the export tests will run instead of being skipped.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v1.16] Add support for /export API

2 participants