From 814f5a969f0a3f310fddc086b1c0db934d01a1f8 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Tue, 18 Nov 2025 14:30:01 +0500 Subject: [PATCH 01/11] feat: Adding support Multimodal embedders. --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 22644da0..a4fac860 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -58,9 +58,9 @@ # This value contains a list of modules to be mocked up. autodoc_mock_imports = ["camel_converter"] -html_title = 'Meilisearch Python | Documentation' +html_title = "Meilisearch Python | Documentation" # Add Fathom analytics script html_js_files = [ - ("https://cdn.usefathom.com/script.js", { "data-site": "QNBPJXIV", "defer": "defer" }) + ("https://cdn.usefathom.com/script.js", {"data-site": "QNBPJXIV", "defer": "defer"}) ] From 2aa6057cacf26695d7a2de1d7360416421b94e98 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Wed, 19 Nov 2025 12:30:30 +0500 Subject: [PATCH 02/11] feat: Adding support for index renaming. --- meilisearch/client.py | 68 ++++++++++++++++++++++++++++++------------- meilisearch/index.py | 16 ++++++++++ 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index 22b1c70f..ba490176 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -531,27 +531,6 @@ def create_snapshot(self) -> TaskInfo: return TaskInfo(**task) - def swap_indexes(self, parameters: List[Mapping[str, List[str]]]) -> TaskInfo: - """Swap two indexes. - - Parameters - ---------- - indexes: - List of indexes to swap (ex: [{"indexes": ["indexA", "indexB"]}). - - Returns - ------- - task_info: - TaskInfo instance containing information about a task to track the progress of an asynchronous process. - https://www.meilisearch.com/docs/reference/api/tasks#get-one-task - - Raises - ------ - MeilisearchApiError - 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 - """ - return TaskInfo(**self.http.post(self.config.paths.swap, parameters)) - def get_tasks(self, parameters: Optional[MutableMapping[str, Any]] = None) -> TaskResults: """Get all tasks. @@ -996,3 +975,50 @@ def _valid_uuid(uuid: str) -> bool: ) match = uuid4hex.match(uuid) return bool(match) + + def get_experimental_features(self) -> dict: + """Get current experimental features settings.""" + return self.http.get(self.config.paths.experimental_features) + + def update_experimental_features(self, features: dict) -> dict: + """Update experimental features settings.""" + return self.http.patch(self.config.paths.experimental_features, body=features) + + def enable_multimodal(self) -> dict: + """Enable multimodal experimental feature.""" + return self.update_experimental_features({"multimodal": True}) + + def disable_multimodal(self) -> dict: + """Disable multimodal experimental feature.""" + return self.update_experimental_features({"multimodal": False}) + + def swap_indexes(self, swaps: List[Dict[str, list]]) -> TaskInfo: + """ + Swap or rename indexes in Meilisearch. + This method accepts a list of swap instructions. + Each instruction must contain: + + - "indexes": a list of exactly two index UIDs + - "rename" (optional): boolean flag + * False (default): swap two existing indexes + * True: rename index_a → index_b (index_b must NOT exist) + + A single request can perform multiple swap or rename operations. + All operations in the request are atomic—either all succeed, or none do. + + Example: + [ + {"indexes": ["A", "B"]}, + {"indexes": ["C_tmp", "C"], "rename": True} + ] + + Returns + ------- + TaskInfo + Task information for the asynchronous swap/rename task. + """ + if not swaps or not all("indexes" in s and len(s["indexes"]) == 2 for s in swaps): + raise ValueError("Each swap must contain exactly two index UIDs under 'indexes' key.") + + task = self.http.post("/swap-indexes",swaps) + return TaskInfo(**task) diff --git a/meilisearch/index.py b/meilisearch/index.py index 0207f1d5..60b0c685 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -2347,3 +2347,19 @@ def compact(self) -> TaskInfo: path = f"{self.config.paths.index}/{self.uid}/compact" task = self.http.post(path) return TaskInfo(**task) + + def rename_index(self, new_name: str) -> TaskInfo: + """ + Rename the current Meilisearch index. + + :param new_name: The new UID for the index. + :return: TaskInfo with information about the rename operation. + """ + payload = {"uid": new_name} + + task = self.http.patch( + f"{self.config.paths.index}/{self.uid}", + payload, + ) + + return TaskInfo(**task) From b50209a04c01fb5a2f087b568babd8b1a09495ad Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Wed, 19 Nov 2025 12:45:49 +0500 Subject: [PATCH 03/11] feat: Adding support for index renaming. --- meilisearch/client.py | 24 ++++++++++++++++++++++++ meilisearch/index.py | 27 ++++++++++----------------- tests/index/test_index.py | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index ba490176..37525992 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -531,6 +531,30 @@ def create_snapshot(self) -> TaskInfo: return TaskInfo(**task) + def swap_indexes(self, parameters: List[Mapping[str, List[str]]]) -> TaskInfo: + """Swap two indexes. + + Parameters + ---------- + indexes: + List of indexes to swap ex: + [{"indexes": ["indexA", "indexB"]}) # default rename to false + {"indexes": ["indexA", "indexB"], "rename": false} + {"indexes": ["indexA", "indexB"], "rename": true} + + Returns + ------- + task_info: + TaskInfo instance containing information about a task to track the progress of an asynchronous process. + https://www.meilisearch.com/docs/reference/api/tasks#get-one-task + + Raises + ------ + MeilisearchApiError + 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 + """ + return TaskInfo(**self.http.post(self.config.paths.swap, parameters)) + def get_tasks(self, parameters: Optional[MutableMapping[str, Any]] = None) -> TaskResults: """Get all tasks. diff --git a/meilisearch/index.py b/meilisearch/index.py index 60b0c685..dcb2c7c7 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -104,13 +104,15 @@ def delete(self) -> TaskInfo: return TaskInfo(**task) - def update(self, primary_key: str) -> TaskInfo: + def update(self, primary_key: Optional[str] = None, new_uid: Optional[str] = None) -> TaskInfo: """Update the index primary-key. Parameters ---------- primary_key: The primary key to use for the index. + new_uid : str, optional + The new UID to rename the index. Returns ------- @@ -123,7 +125,13 @@ def update(self, primary_key: str) -> TaskInfo: MeilisearchApiError 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 """ - payload = {"primaryKey": primary_key} + payload = {} + if primary_key is not None: + payload["primaryKey"] = primary_key + + if new_uid is not None: + payload["uid"] = new_uid # This enables renaming + task = self.http.patch(f"{self.config.paths.index}/{self.uid}", payload) return TaskInfo(**task) @@ -2348,18 +2356,3 @@ def compact(self) -> TaskInfo: task = self.http.post(path) return TaskInfo(**task) - def rename_index(self, new_name: str) -> TaskInfo: - """ - Rename the current Meilisearch index. - - :param new_name: The new UID for the index. - :return: TaskInfo with information about the rename operation. - """ - payload = {"uid": new_name} - - task = self.http.patch( - f"{self.config.paths.index}/{self.uid}", - payload, - ) - - return TaskInfo(**task) diff --git a/tests/index/test_index.py b/tests/index/test_index.py index 397b6e0c..8158f6fa 100644 --- a/tests/index/test_index.py +++ b/tests/index/test_index.py @@ -234,3 +234,42 @@ def test_index_compact(client): assert stats_before.number_of_documents == stats_after.number_of_documents assert stats_after.is_indexing is False + + +@pytest.mark.usefixtures("indexes_sample") +def test_rename_index(client): + """Test renaming an existing index.""" + original_uid = common.INDEX_UID + new_uid = f"{original_uid}_renamed" + index = client.index(original_uid) + + # Perform the rename + task_info = index.update(new_uid=new_uid) + client.wait_for_task(task_info.task_uid) + + # Verify the index now exists with the new UID + renamed_index = client.index(new_uid) + info = renamed_index.fetch_info() + assert info.uid == new_uid + + # # Verify the old UID no longer exists + with pytest.raises(MeilisearchApiError): + client.index(original_uid).fetch_info() # Assert old UID is gone + + +@pytest.mark.usefixtures("indexes_sample") +def test_index_update_and_rename(client): + """Test updating primary key and renaming an index together.""" + original_uid = common.INDEX_UID + new_uid = f"{original_uid}_renamed" + index = client.index(original_uid) + + # 1. Update the primary key + task_info = index.update(primary_key="objectID", new_uid=new_uid) + client.wait_for_task(task_info.task_uid) + + # Verify the index now exists with the new UID + renamed_index = client.index(new_uid) + info = renamed_index.fetch_info() + assert info.uid == new_uid + assert renamed_index.get_primary_key() == "objectID" From 4359f7944bbed3ff370c69d774ff123fa8e01141 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 20 Nov 2025 16:12:00 +0500 Subject: [PATCH 04/11] feat: Adding support for index renaming. --- meilisearch/client.py | 47 ------------------------------------------- 1 file changed, 47 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index 37525992..77dee8cd 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -999,50 +999,3 @@ def _valid_uuid(uuid: str) -> bool: ) match = uuid4hex.match(uuid) return bool(match) - - def get_experimental_features(self) -> dict: - """Get current experimental features settings.""" - return self.http.get(self.config.paths.experimental_features) - - def update_experimental_features(self, features: dict) -> dict: - """Update experimental features settings.""" - return self.http.patch(self.config.paths.experimental_features, body=features) - - def enable_multimodal(self) -> dict: - """Enable multimodal experimental feature.""" - return self.update_experimental_features({"multimodal": True}) - - def disable_multimodal(self) -> dict: - """Disable multimodal experimental feature.""" - return self.update_experimental_features({"multimodal": False}) - - def swap_indexes(self, swaps: List[Dict[str, list]]) -> TaskInfo: - """ - Swap or rename indexes in Meilisearch. - This method accepts a list of swap instructions. - Each instruction must contain: - - - "indexes": a list of exactly two index UIDs - - "rename" (optional): boolean flag - * False (default): swap two existing indexes - * True: rename index_a → index_b (index_b must NOT exist) - - A single request can perform multiple swap or rename operations. - All operations in the request are atomic—either all succeed, or none do. - - Example: - [ - {"indexes": ["A", "B"]}, - {"indexes": ["C_tmp", "C"], "rename": True} - ] - - Returns - ------- - TaskInfo - Task information for the asynchronous swap/rename task. - """ - if not swaps or not all("indexes" in s and len(s["indexes"]) == 2 for s in swaps): - raise ValueError("Each swap must contain exactly two index UIDs under 'indexes' key.") - - task = self.http.post("/swap-indexes",swaps) - return TaskInfo(**task) From 4864aead713897f8f49174aaed5f5184da8ca609 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 20 Nov 2025 16:36:22 +0500 Subject: [PATCH 05/11] feat: Adding support for index renaming. --- tests/client/test_client_swap_meilisearch.py | 34 ++++++++++++++++++++ tests/common.py | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/client/test_client_swap_meilisearch.py b/tests/client/test_client_swap_meilisearch.py index dabb7514..992503eb 100644 --- a/tests/client/test_client_swap_meilisearch.py +++ b/tests/client/test_client_swap_meilisearch.py @@ -45,6 +45,20 @@ def test_swap_indexes_with_one_that_does_not_exist(client, empty_index): assert swapTask.type == "indexSwap" assert task.error["code"] == "index_not_found" +def test_swap_indexes_with_one_that_does_not_exist_with_rename_as_false(client, empty_index): + """Tests swap indexes with one that does not exist.""" + index = empty_index("index_A") + swapTask = client.swap_indexes( + [ + { + "indexes": [index.uid, "does_not_exist"], "rename": False + }, + ] + ) + task = client.wait_for_task(swapTask.task_uid) + + assert swapTask.type == "indexSwap" + assert task.error["code"] == "index_not_found" def test_swap_indexes_with_itself(client, empty_index): """Tests swap indexes with itself.""" @@ -57,3 +71,23 @@ def test_swap_indexes_with_itself(client, empty_index): }, ] ) + + +def test_swap_indexes_with_one_that_does_not_exist_with_rename_as_true(client, empty_index): + """Tests swap indexes with one that does not exist.""" + index = empty_index("index_B") + renamed_index_name = "new_index_name" + swapTask = client.swap_indexes( + [ + { + "indexes": [index.uid, renamed_index_name], "rename": True + }, + ] + ) + client.wait_for_task(swapTask.task_uid) + assert swapTask.type == "indexSwap" + + # Verify the new index UID exists + renamed_index = client.index(renamed_index_name) + info = renamed_index.fetch_info() + assert info.uid == renamed_index_name diff --git a/tests/common.py b/tests/common.py index 253ec760..0842c49e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,6 +1,6 @@ import os -MASTER_KEY = "masterKey" +MASTER_KEY = "aSampleMasterKey" BASE_URL = os.getenv("MEILISEARCH_URL", "http://127.0.0.1:7700") INDEX_UID = "indexUID" From a15246e14c573f08d1b0cf1c137eddbc79967fe7 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 20 Nov 2025 16:37:10 +0500 Subject: [PATCH 06/11] feat: Adding support for index renaming. --- tests/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common.py b/tests/common.py index 0842c49e..253ec760 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,6 +1,6 @@ import os -MASTER_KEY = "aSampleMasterKey" +MASTER_KEY = "masterKey" BASE_URL = os.getenv("MEILISEARCH_URL", "http://127.0.0.1:7700") INDEX_UID = "indexUID" From 85649c273540fdf4820e36ec97a357ac395561a7 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 20 Nov 2025 16:43:04 +0500 Subject: [PATCH 07/11] feat: Adding support for index renaming. --- meilisearch/index.py | 1 - tests/client/test_client_swap_meilisearch.py | 10 ++++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/meilisearch/index.py b/meilisearch/index.py index dcb2c7c7..38e8d2f1 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -2355,4 +2355,3 @@ def compact(self) -> TaskInfo: path = f"{self.config.paths.index}/{self.uid}/compact" task = self.http.post(path) return TaskInfo(**task) - diff --git a/tests/client/test_client_swap_meilisearch.py b/tests/client/test_client_swap_meilisearch.py index 992503eb..ae3b9914 100644 --- a/tests/client/test_client_swap_meilisearch.py +++ b/tests/client/test_client_swap_meilisearch.py @@ -45,14 +45,13 @@ def test_swap_indexes_with_one_that_does_not_exist(client, empty_index): assert swapTask.type == "indexSwap" assert task.error["code"] == "index_not_found" + def test_swap_indexes_with_one_that_does_not_exist_with_rename_as_false(client, empty_index): """Tests swap indexes with one that does not exist.""" index = empty_index("index_A") swapTask = client.swap_indexes( [ - { - "indexes": [index.uid, "does_not_exist"], "rename": False - }, + {"indexes": [index.uid, "does_not_exist"], "rename": False}, ] ) task = client.wait_for_task(swapTask.task_uid) @@ -60,6 +59,7 @@ def test_swap_indexes_with_one_that_does_not_exist_with_rename_as_false(client, assert swapTask.type == "indexSwap" assert task.error["code"] == "index_not_found" + def test_swap_indexes_with_itself(client, empty_index): """Tests swap indexes with itself.""" index = empty_index() @@ -79,9 +79,7 @@ def test_swap_indexes_with_one_that_does_not_exist_with_rename_as_true(client, e renamed_index_name = "new_index_name" swapTask = client.swap_indexes( [ - { - "indexes": [index.uid, renamed_index_name], "rename": True - }, + {"indexes": [index.uid, renamed_index_name], "rename": True}, ] ) client.wait_for_task(swapTask.task_uid) From fb58d6dc21d657785326dbf3c4e8e3598582a1a1 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 20 Nov 2025 17:21:44 +0500 Subject: [PATCH 08/11] feat: Adding support for index renaming. --- meilisearch/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index 77dee8cd..022e8c1a 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -538,9 +538,9 @@ def swap_indexes(self, parameters: List[Mapping[str, List[str]]]) -> TaskInfo: ---------- indexes: List of indexes to swap ex: - [{"indexes": ["indexA", "indexB"]}) # default rename to false - {"indexes": ["indexA", "indexB"], "rename": false} - {"indexes": ["indexA", "indexB"], "rename": true} + 1: {"indexes": ["indexA", "indexB"]} # default rename to false + 2: {"indexes": ["indexA", "indexB"], "rename": false} + 3: {"indexes": ["indexA", "indexB"], "rename": true} Returns ------- From 7fd5dbe93d4760e7455845a5b9edc174493bd841 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 20 Nov 2025 17:40:11 +0500 Subject: [PATCH 09/11] feat: Adding support for index renaming. --- meilisearch/index.py | 3 +++ tests/index/test_index.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/meilisearch/index.py b/meilisearch/index.py index 38e8d2f1..f7bd644b 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -125,6 +125,9 @@ def update(self, primary_key: Optional[str] = None, new_uid: Optional[str] = Non MeilisearchApiError 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 """ + if primary_key is None and new_uid is None: + raise ValueError("You must provide either 'primary_key' or 'new_uid' to update the index.") + payload = {} if primary_key is not None: payload["primaryKey"] = primary_key diff --git a/tests/index/test_index.py b/tests/index/test_index.py index 8158f6fa..38b11943 100644 --- a/tests/index/test_index.py +++ b/tests/index/test_index.py @@ -273,3 +273,13 @@ def test_index_update_and_rename(client): info = renamed_index.fetch_info() assert info.uid == new_uid assert renamed_index.get_primary_key() == "objectID" + + +@pytest.mark.usefixtures("indexes_sample") +def test_index_update_without_params(client): + """Test updating primary key and renaming an index together.""" + index = client.index(common.INDEX_UID) + with pytest.raises(ValueError) as exc: + index.update() + + assert "primary_key" in str(exc.value) or "new_uid" in str(exc.value) From 1f6ee1acb261dea96160cd01109a9873772e0fa2 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 20 Nov 2025 17:41:59 +0500 Subject: [PATCH 10/11] feat: Adding support for index renaming. --- meilisearch/index.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/meilisearch/index.py b/meilisearch/index.py index f7bd644b..23d01242 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -126,7 +126,9 @@ def update(self, primary_key: Optional[str] = None, new_uid: Optional[str] = Non 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 """ if primary_key is None and new_uid is None: - raise ValueError("You must provide either 'primary_key' or 'new_uid' to update the index.") + raise ValueError( + "You must provide either 'primary_key' or 'new_uid' to update the index." + ) payload = {} if primary_key is not None: From e9141305f23f9d1d892a2a36d3435144ddebcfe0 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Mon, 24 Nov 2025 15:37:15 +0500 Subject: [PATCH 11/11] feat: Adding support for index renaming. --- meilisearch/index.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/meilisearch/index.py b/meilisearch/index.py index 23d01242..9abe832f 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -114,6 +114,15 @@ def update(self, primary_key: Optional[str] = None, new_uid: Optional[str] = Non new_uid : str, optional The new UID to rename the index. + Renaming behavior + ----------------- + When ``new_uid`` is provided, this method sends a PATCH request to rename + the index. After the task completes, the index exists under the new UID, + but this ``Index`` instance still contains the old ``self.uid``, making it + **stale**. Further operations with this instance will fail until a fresh + instance is obtained. After the rename task completes, obtain a new ``Index`` + instance via ``client.index(new_uid)`` before making further requests. + Returns ------- task_info: