From 7af27f2932b4b66477dd0b86c4c8eb56293f7331 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 1 Jan 2026 18:19:39 +0500 Subject: [PATCH 1/8] feat: Adds an optional skipCreation. When set to true, documents that don't exist in the index are silently ignored rather than created. --- meilisearch/index.py | 77 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/meilisearch/index.py b/meilisearch/index.py index cfbff045..388ff6b7 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -454,6 +454,7 @@ def add_documents( primary_key: Optional[str] = None, *, serializer: Optional[Type[JSONEncoder]] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Add documents to the index. @@ -466,6 +467,9 @@ def add_documents( serializer (optional): A custom JSONEncode to handle serializing fields that the build in json.dumps cannot handle, for example UUID and datetime. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -478,7 +482,7 @@ def add_documents( 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 """ - url = self._build_url(primary_key) + url = self._build_url(primary_key, skip_creation=skip_creation) add_document_task = self.http.post(url, documents, serializer=serializer) return TaskInfo(**add_document_task) @@ -489,6 +493,7 @@ def add_documents_in_batches( primary_key: Optional[str] = None, *, serializer: Optional[Type[JSONEncoder]] = None, + skip_creation: Optional[bool] = None, ) -> List[TaskInfo]: """Add documents to the index in batches. @@ -503,6 +508,9 @@ def add_documents_in_batches( serializer (optional): A custom JSONEncode to handle serializing fields that the build in json.dumps cannot handle, for example UUID and datetime. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -520,7 +528,7 @@ def add_documents_in_batches( tasks: List[TaskInfo] = [] for document_batch in self._batch(documents, batch_size): - task = self.add_documents(document_batch, primary_key, serializer=serializer) + task = self.add_documents(document_batch, primary_key, serializer=serializer, skip_creation=skip_creation) tasks.append(task) return tasks @@ -531,6 +539,7 @@ def add_documents_json( primary_key: Optional[str] = None, *, serializer: Optional[Type[JSONEncoder]] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Add documents to the index from a byte-encoded JSON string. @@ -543,6 +552,9 @@ def add_documents_json( serializer (optional): A custom JSONEncode to handle serializing fields that the build in json.dumps cannot handle, for example UUID and datetime. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -556,7 +568,7 @@ def add_documents_json( 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 self.add_documents_raw( - str_documents, primary_key, "application/json", serializer=serializer + str_documents, primary_key, "application/json", serializer=serializer, skip_creation=skip_creation ) def add_documents_csv( @@ -564,6 +576,7 @@ def add_documents_csv( str_documents: bytes, primary_key: Optional[str] = None, csv_delimiter: Optional[str] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Add documents to the index from a byte-encoded CSV string. @@ -575,6 +588,9 @@ def add_documents_csv( The primary-key used in index. Ignored if already set up. csv_delimiter: One ASCII character used to customize the delimiter for CSV. Comma used by default. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -587,12 +603,13 @@ def add_documents_csv( 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 self.add_documents_raw(str_documents, primary_key, "text/csv", csv_delimiter) + return self.add_documents_raw(str_documents, primary_key, "text/csv", csv_delimiter, skip_creation=skip_creation) def add_documents_ndjson( self, str_documents: bytes, primary_key: Optional[str] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Add documents to the index from a byte-encoded NDJSON string. @@ -602,6 +619,9 @@ def add_documents_ndjson( Byte-encoded NDJSON string. primary_key (optional): The primary-key used in index. Ignored if already set up. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -614,7 +634,7 @@ def add_documents_ndjson( 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 self.add_documents_raw(str_documents, primary_key, "application/x-ndjson") + return self.add_documents_raw(str_documents, primary_key, "application/x-ndjson", skip_creation=skip_creation) def add_documents_raw( self, @@ -624,6 +644,7 @@ def add_documents_raw( csv_delimiter: Optional[str] = None, *, serializer: Optional[Type[JSONEncoder]] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Add documents to the index from a byte-encoded string. @@ -641,6 +662,9 @@ def add_documents_raw( serializer (optional): A custom JSONEncode to handle serializing fields that the build in json.dumps cannot handle, for example UUID and datetime. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -653,7 +677,7 @@ def add_documents_raw( 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 """ - url = self._build_url(primary_key=primary_key, csv_delimiter=csv_delimiter) + url = self._build_url(primary_key=primary_key, csv_delimiter=csv_delimiter, skip_creation=skip_creation) response = self.http.post(url, str_documents, content_type, serializer=serializer) return TaskInfo(**response) @@ -663,6 +687,7 @@ def update_documents( primary_key: Optional[str] = None, *, serializer: Optional[Type[JSONEncoder]] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Update documents in the index. @@ -675,6 +700,9 @@ def update_documents( serializer (optional): A custom JSONEncode to handle serializing fields that the build in json.dumps cannot handle, for example UUID and datetime. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -687,7 +715,7 @@ def update_documents( 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 """ - url = self._build_url(primary_key) + url = self._build_url(primary_key, skip_creation=skip_creation) response = self.http.put(url, documents, serializer=serializer) return TaskInfo(**response) @@ -695,6 +723,7 @@ def update_documents_ndjson( self, str_documents: str, primary_key: Optional[str] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Update documents as a ndjson string in the index. @@ -704,6 +733,9 @@ def update_documents_ndjson( String of document from a NDJSON file. primary_key (optional): The primary-key used in index. Ignored if already set up + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -716,7 +748,7 @@ def update_documents_ndjson( 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 self.update_documents_raw(str_documents, primary_key, "application/x-ndjson") + return self.update_documents_raw(str_documents, primary_key, "application/x-ndjson", skip_creation=skip_creation) def update_documents_json( self, @@ -724,6 +756,7 @@ def update_documents_json( primary_key: Optional[str] = None, *, serializer: Optional[Type[JSONEncoder]] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Update documents as a json string in the index. @@ -736,6 +769,9 @@ def update_documents_json( serializer (optional): A custom JSONEncode to handle serializing fields that the build in json.dumps cannot handle, for example UUID and datetime. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -749,7 +785,7 @@ def update_documents_json( 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 self.update_documents_raw( - str_documents, primary_key, "application/json", serializer=serializer + str_documents, primary_key, "application/json", serializer=serializer, skip_creation=skip_creation ) def update_documents_csv( @@ -757,6 +793,7 @@ def update_documents_csv( str_documents: str, primary_key: Optional[str] = None, csv_delimiter: Optional[str] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Update documents as a csv string in the index. @@ -768,6 +805,9 @@ def update_documents_csv( The primary-key used in index. Ignored if already set up. csv_delimiter: One ASCII character used to customize the delimiter for CSV. Comma used by default. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -780,7 +820,7 @@ def update_documents_csv( 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 self.update_documents_raw(str_documents, primary_key, "text/csv", csv_delimiter) + return self.update_documents_raw(str_documents, primary_key, "text/csv", csv_delimiter, skip_creation=skip_creation) def update_documents_raw( self, @@ -790,6 +830,7 @@ def update_documents_raw( csv_delimiter: Optional[str] = None, *, serializer: Optional[Type[JSONEncoder]] = None, + skip_creation: Optional[bool] = None, ) -> TaskInfo: """Update documents as a string in the index. @@ -807,6 +848,9 @@ def update_documents_raw( serializer (optional): A custom JSONEncode to handle serializing fields that the build in json.dumps cannot handle, for example UUID and datetime. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -819,7 +863,7 @@ def update_documents_raw( 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 """ - url = self._build_url(primary_key=primary_key, csv_delimiter=csv_delimiter) + url = self._build_url(primary_key=primary_key, csv_delimiter=csv_delimiter, skip_creation=skip_creation) response = self.http.put(url, str_documents, content_type, serializer=serializer) return TaskInfo(**response) @@ -829,6 +873,7 @@ def update_documents_in_batches( batch_size: int = 1000, primary_key: Optional[str] = None, serializer: Optional[Type[JSONEncoder]] = None, + skip_creation: Optional[bool] = None, ) -> List[TaskInfo]: """Update documents to the index in batches. @@ -843,6 +888,9 @@ def update_documents_in_batches( serializer (optional): A custom JSONEncode to handle serializing fields that the build in json.dumps cannot handle, for example UUID and datetime. + skip_creation (optional): + If True, documents that don't exist in the index are silently ignored rather + than created. Default is False, preserving existing behavior. Returns ------- @@ -860,7 +908,7 @@ def update_documents_in_batches( tasks = [] for document_batch in self._batch(documents, batch_size): - update_task = self.update_documents(document_batch, primary_key, serializer=serializer) + update_task = self.update_documents(document_batch, primary_key, serializer=serializer, skip_creation=skip_creation) tasks.append(update_task) return tasks @@ -2331,13 +2379,16 @@ def _build_url( self, primary_key: Optional[str] = None, csv_delimiter: Optional[str] = None, + skip_creation: Optional[bool] = None, ) -> str: parameters = {} if primary_key: parameters["primaryKey"] = primary_key if csv_delimiter: parameters["csvDelimiter"] = csv_delimiter - if primary_key is None and csv_delimiter is None: + if skip_creation is True: + parameters["skipCreation"] = "true" + if not parameters: return f"{self.config.paths.index}/{self.uid}/{self.config.paths.document}" return f"{self.config.paths.index}/{self.uid}/{self.config.paths.document}?{parse.urlencode(parameters)}" From 917838045e8703540ccb72f0b4ac094c40c8b8dd Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 1 Jan 2026 18:27:22 +0500 Subject: [PATCH 2/8] feat: Adds an optional skipCreation. When set to true, documents that don't exist in the index are silently ignored rather than created. --- meilisearch/index.py | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/meilisearch/index.py b/meilisearch/index.py index 388ff6b7..5325a125 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -528,7 +528,9 @@ def add_documents_in_batches( tasks: List[TaskInfo] = [] for document_batch in self._batch(documents, batch_size): - task = self.add_documents(document_batch, primary_key, serializer=serializer, skip_creation=skip_creation) + task = self.add_documents( + document_batch, primary_key, serializer=serializer, skip_creation=skip_creation + ) tasks.append(task) return tasks @@ -568,7 +570,11 @@ def add_documents_json( 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 self.add_documents_raw( - str_documents, primary_key, "application/json", serializer=serializer, skip_creation=skip_creation + str_documents, + primary_key, + "application/json", + serializer=serializer, + skip_creation=skip_creation, ) def add_documents_csv( @@ -603,7 +609,9 @@ def add_documents_csv( 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 self.add_documents_raw(str_documents, primary_key, "text/csv", csv_delimiter, skip_creation=skip_creation) + return self.add_documents_raw( + str_documents, primary_key, "text/csv", csv_delimiter, skip_creation=skip_creation + ) def add_documents_ndjson( self, @@ -634,7 +642,9 @@ def add_documents_ndjson( 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 self.add_documents_raw(str_documents, primary_key, "application/x-ndjson", skip_creation=skip_creation) + return self.add_documents_raw( + str_documents, primary_key, "application/x-ndjson", skip_creation=skip_creation + ) def add_documents_raw( self, @@ -677,7 +687,9 @@ def add_documents_raw( 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 """ - url = self._build_url(primary_key=primary_key, csv_delimiter=csv_delimiter, skip_creation=skip_creation) + url = self._build_url( + primary_key=primary_key, csv_delimiter=csv_delimiter, skip_creation=skip_creation + ) response = self.http.post(url, str_documents, content_type, serializer=serializer) return TaskInfo(**response) @@ -748,7 +760,9 @@ def update_documents_ndjson( 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 self.update_documents_raw(str_documents, primary_key, "application/x-ndjson", skip_creation=skip_creation) + return self.update_documents_raw( + str_documents, primary_key, "application/x-ndjson", skip_creation=skip_creation + ) def update_documents_json( self, @@ -785,7 +799,11 @@ def update_documents_json( 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 self.update_documents_raw( - str_documents, primary_key, "application/json", serializer=serializer, skip_creation=skip_creation + str_documents, + primary_key, + "application/json", + serializer=serializer, + skip_creation=skip_creation, ) def update_documents_csv( @@ -820,7 +838,9 @@ def update_documents_csv( 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 self.update_documents_raw(str_documents, primary_key, "text/csv", csv_delimiter, skip_creation=skip_creation) + return self.update_documents_raw( + str_documents, primary_key, "text/csv", csv_delimiter, skip_creation=skip_creation + ) def update_documents_raw( self, @@ -863,7 +883,9 @@ def update_documents_raw( 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 """ - url = self._build_url(primary_key=primary_key, csv_delimiter=csv_delimiter, skip_creation=skip_creation) + url = self._build_url( + primary_key=primary_key, csv_delimiter=csv_delimiter, skip_creation=skip_creation + ) response = self.http.put(url, str_documents, content_type, serializer=serializer) return TaskInfo(**response) @@ -908,7 +930,9 @@ def update_documents_in_batches( tasks = [] for document_batch in self._batch(documents, batch_size): - update_task = self.update_documents(document_batch, primary_key, serializer=serializer, skip_creation=skip_creation) + update_task = self.update_documents( + document_batch, primary_key, serializer=serializer, skip_creation=skip_creation + ) tasks.append(update_task) return tasks From f0333d4727563d6ddbb1356861e2b2c98fe0f7e7 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 1 Jan 2026 18:41:27 +0500 Subject: [PATCH 3/8] feat: Adds an optional skipCreation. When set to true, documents that don't exist in the index are silently ignored rather than created. --- .../index/test_index_document_meilisearch.py | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) diff --git a/tests/index/test_index_document_meilisearch.py b/tests/index/test_index_document_meilisearch.py index 35bc5f33..256d0e1d 100644 --- a/tests/index/test_index_document_meilisearch.py +++ b/tests/index/test_index_document_meilisearch.py @@ -555,3 +555,270 @@ def test_update_documents_ndjson(index_with_documents, songs_ndjson): task = index.wait_for_task(response.task_uid) assert task.status == "succeeded" assert index.get_primary_key() == "id" + + +# Tests for skip_creation parameter +def test_add_documents_with_skip_creation_true(empty_index): + """Tests that skip_creation=True prevents creation of new documents.""" + index = empty_index() + documents = [ + {"id": "1", "title": "Existing Document"}, + ] + + # First add a document normally + task = index.add_documents(documents) + index.wait_for_task(task.task_uid) + + # Verify document exists + doc = index.get_document("1") + assert doc.title == "Existing Document" + + # Now try to add a new document with skip_creation=True - should be ignored + new_documents = [ + {"id": "2", "title": "New Document"}, + ] + task = index.add_documents(new_documents, skip_creation=True) + index.wait_for_task(task.task_uid) + + # New document should not exist + with pytest.raises(Exception): + index.get_document("2") + + # Existing document should still be there + doc = index.get_document("1") + assert doc.title == "Existing Document" + + +def test_add_documents_with_skip_creation_false(empty_index): + """Tests that skip_creation=False allows creation of new documents (default behavior).""" + index = empty_index() + documents = [ + {"id": "1", "title": "New Document"}, + ] + + # Add document with skip_creation=False (should work same as default) + task = index.add_documents(documents, skip_creation=False) + index.wait_for_task(task.task_uid) + + # Document should exist + doc = index.get_document("1") + assert doc.title == "New Document" + + +def test_add_documents_skip_creation_updates_existing(empty_index): + """Tests that skip_creation=True still allows updating existing documents.""" + index = empty_index() + documents = [ + {"id": "1", "title": "Original Title"}, + ] + + # Add document initially + task = index.add_documents(documents) + index.wait_for_task(task.task_uid) + + # Update with skip_creation=True - should update existing document + updated_documents = [ + {"id": "1", "title": "Updated Title"}, + ] + task = index.add_documents(updated_documents, skip_creation=True) + index.wait_for_task(task.task_uid) + + # Document should be updated + doc = index.get_document("1") + assert doc.title == "Updated Title" + + +def test_update_documents_with_skip_creation_true(empty_index): + """Tests that update_documents with skip_creation=True prevents creation of new documents.""" + index = empty_index() + documents = [ + {"id": "1", "title": "Existing Document"}, + ] + + # First add a document + task = index.add_documents(documents) + index.wait_for_task(task.task_uid) + + # Now try to update with a new document - should be ignored + new_documents = [ + {"id": "2", "title": "New Document"}, + ] + task = index.update_documents(new_documents, skip_creation=True) + index.wait_for_task(task.task_uid) + + # New document should not exist + with pytest.raises(Exception): + index.get_document("2") + + # Existing document should still be there and unchanged + doc = index.get_document("1") + assert doc.title == "Existing Document" + + +def test_update_documents_skip_creation_updates_existing(empty_index): + """Tests that update_documents with skip_creation=True still updates existing documents.""" + index = empty_index() + documents = [ + {"id": "1", "title": "Original Title"}, + ] + + # Add document initially + task = index.add_documents(documents) + index.wait_for_task(task.task_uid) + + # Update with skip_creation=True - should update existing document + updated_documents = [ + {"id": "1", "title": "Updated Title"}, + ] + task = index.update_documents(updated_documents, skip_creation=True) + index.wait_for_task(task.task_uid) + + # Document should be updated + doc = index.get_document("1") + assert doc.title == "Updated Title" + + +def test_add_documents_in_batches_with_skip_creation(empty_index, small_movies): + """Tests that skip_creation parameter works with add_documents_in_batches.""" + index = empty_index() + + # Add some documents first + initial_docs = small_movies[:5] + task = index.add_documents(initial_docs) + index.wait_for_task(task.task_uid) + + # Try to add more documents with skip_creation=True + new_docs = small_movies[5:10] + task = index.add_documents_in_batches(new_docs, batch_size=2, skip_creation=True) + assert isinstance(task, list) + for t in task: + index.wait_for_task(t.task_uid) + + # Only original documents should exist + all_docs = index.get_documents().results + existing_ids = {doc.id for doc in all_docs} + original_ids = {doc["id"] for doc in initial_docs} + assert existing_ids == original_ids + + +def test_update_documents_in_batches_with_skip_creation(empty_index, small_movies): + """Tests that skip_creation parameter works with update_documents_in_batches.""" + index = empty_index() + + # Add some documents first + initial_docs = small_movies[:5] + task = index.add_documents(initial_docs) + index.wait_for_task(task.task_uid) + + # Try to update with new documents with skip_creation=True + new_docs = small_movies[5:10] + task = index.update_documents_in_batches(new_docs, batch_size=2, skip_creation=True) + assert isinstance(task, list) + for t in task: + index.wait_for_task(t.task_uid) + + # Only original documents should exist + all_docs = index.get_documents().results + existing_ids = {doc.id for doc in all_docs} + original_ids = {doc["id"] for doc in initial_docs} + assert existing_ids == original_ids + + +def test_add_documents_json_with_skip_creation(empty_index, small_movies_json_file): + """Tests that skip_creation parameter works with add_documents_json.""" + import json + + index = empty_index() + documents = json.loads(small_movies_json_file.decode("utf-8")) + + # Add first document + first_doc = json.dumps([documents[0]]).encode("utf-8") + task = index.add_documents_json(first_doc) + index.wait_for_task(task.task_uid) + + # Try to add new document with skip_creation=True + new_doc = json.dumps([documents[1]]).encode("utf-8") + task = index.add_documents_json(new_doc, skip_creation=True) + index.wait_for_task(task.task_uid) + + # Only first document should exist + all_docs = index.get_documents().results + assert len(all_docs) == 1 + assert all_docs[0].id == documents[0]["id"] + + +def test_update_documents_json_with_skip_creation(empty_index, small_movies_json_file): + """Tests that skip_creation parameter works with update_documents_json.""" + import json + + index = empty_index() + documents = json.loads(small_movies_json_file.decode("utf-8")) + + # Add first document + first_doc = json.dumps([documents[0]]).encode("utf-8") + task = index.add_documents(first_doc.decode("utf-8")) + index.wait_for_task(task.task_uid) + + # Try to update with new document with skip_creation=True + new_doc = json.dumps([documents[1]]).encode("utf-8") + task = index.update_documents_json(new_doc.decode("utf-8"), skip_creation=True) + index.wait_for_task(task.task_uid) + + # Only first document should exist + all_docs = index.get_documents().results + assert len(all_docs) == 1 + assert all_docs[0].id == documents[0]["id"] + + +def test_add_documents_csv_with_skip_creation(empty_index, songs_csv): + """Tests that skip_creation parameter works with add_documents_csv.""" + index = empty_index() + + # Add first few lines manually + first_line = songs_csv.split(b"\n")[0] + b"\n" + task = index.add_documents_csv(first_line) + index.wait_for_task(task.task_uid) + + # Try to add more with skip_creation=True + task = index.add_documents_csv(songs_csv, skip_creation=True) + index.wait_for_task(task.task_uid) + + # Only first document should exist + all_docs = index.get_documents().results + assert len(all_docs) == 1 + + +def test_update_documents_csv_with_skip_creation(empty_index, songs_csv): + """Tests that skip_creation parameter works with update_documents_csv.""" + index = empty_index() + + # Add first few lines manually + first_line = songs_csv.split(b"\n")[0] + b"\n" + task = index.add_documents_csv(first_line) + index.wait_for_task(task.task_uid) + + # Try to update with more with skip_creation=True + task = index.update_documents_csv(songs_csv.decode("utf-8"), skip_creation=True) + index.wait_for_task(task.task_uid) + + # Only first document should exist + all_docs = index.get_documents().results + assert len(all_docs) == 1 + + +def test_add_documents_ndjson_with_skip_creation(empty_index, songs_ndjson): + """Tests that skip_creation parameter works with add_documents_ndjson.""" + index = empty_index() + + # Add first line + first_line = songs_ndjson.split(b"\n")[0] + b"\n" + task = index.add_documents_ndjson(first_line) + index.wait_for_task(task.task_uid) + + # Try to add more with skip_creation=True + task = index.add_documents_ndjson(songs_ndjson, skip_creation=True) + index.wait_for_task(task.task_uid) + + # Only first document should exist + all_docs = index.get_documents().results + assert len(all_docs) == 1 From 0c3ee2a4fec68526c67e924b8aaac527c774db0f Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 1 Jan 2026 18:49:12 +0500 Subject: [PATCH 4/8] feat: Adds an optional skipCreation. When set to true, documents that don't exist in the index are silently ignored rather than created. --- .../index/test_index_document_meilisearch.py | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/tests/index/test_index_document_meilisearch.py b/tests/index/test_index_document_meilisearch.py index 256d0e1d..318d3144 100644 --- a/tests/index/test_index_document_meilisearch.py +++ b/tests/index/test_index_document_meilisearch.py @@ -754,14 +754,14 @@ def test_update_documents_json_with_skip_creation(empty_index, small_movies_json index = empty_index() documents = json.loads(small_movies_json_file.decode("utf-8")) - # Add first document - first_doc = json.dumps([documents[0]]).encode("utf-8") - task = index.add_documents(first_doc.decode("utf-8")) + # Add first document using add_documents (expects list of dicts) + task = index.add_documents([documents[0]]) index.wait_for_task(task.task_uid) # Try to update with new document with skip_creation=True - new_doc = json.dumps([documents[1]]).encode("utf-8") - task = index.update_documents_json(new_doc.decode("utf-8"), skip_creation=True) + # update_documents_json expects a string (JSON encoded) + new_doc = json.dumps([documents[1]]) + task = index.update_documents_json(new_doc, skip_creation=True) index.wait_for_task(task.task_uid) # Only first document should exist @@ -774,36 +774,48 @@ def test_add_documents_csv_with_skip_creation(empty_index, songs_csv): """Tests that skip_creation parameter works with add_documents_csv.""" index = empty_index() - # Add first few lines manually - first_line = songs_csv.split(b"\n")[0] + b"\n" - task = index.add_documents_csv(first_line) + # Add first two lines (header + first data row) manually + lines = songs_csv.split(b"\n") + first_two_lines = b"\n".join(lines[:2]) + b"\n" + task = index.add_documents_csv(first_two_lines) index.wait_for_task(task.task_uid) + # Verify first document exists + all_docs = index.get_documents().results + initial_count = len(all_docs) + assert initial_count == 1 + # Try to add more with skip_creation=True task = index.add_documents_csv(songs_csv, skip_creation=True) index.wait_for_task(task.task_uid) - # Only first document should exist + # Only first document should exist (no new ones created) all_docs = index.get_documents().results - assert len(all_docs) == 1 + assert len(all_docs) == initial_count def test_update_documents_csv_with_skip_creation(empty_index, songs_csv): """Tests that skip_creation parameter works with update_documents_csv.""" index = empty_index() - # Add first few lines manually - first_line = songs_csv.split(b"\n")[0] + b"\n" - task = index.add_documents_csv(first_line) + # Add first two lines (header + first data row) manually + lines = songs_csv.split(b"\n") + first_two_lines = b"\n".join(lines[:2]) + b"\n" + task = index.add_documents_csv(first_two_lines) index.wait_for_task(task.task_uid) + # Verify first document exists + all_docs = index.get_documents().results + initial_count = len(all_docs) + assert initial_count == 1 + # Try to update with more with skip_creation=True task = index.update_documents_csv(songs_csv.decode("utf-8"), skip_creation=True) index.wait_for_task(task.task_uid) - # Only first document should exist + # Only first document should exist (no new ones created) all_docs = index.get_documents().results - assert len(all_docs) == 1 + assert len(all_docs) == initial_count def test_add_documents_ndjson_with_skip_creation(empty_index, songs_ndjson): From f1778a9c068cc4797b8a3f80e2d00802696b39e2 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 1 Jan 2026 18:53:54 +0500 Subject: [PATCH 5/8] feat: Adds an optional skipCreation. When set to true, documents that don't exist in the index are silently ignored rather than created. --- tests/index/test_index_document_meilisearch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/index/test_index_document_meilisearch.py b/tests/index/test_index_document_meilisearch.py index 318d3144..20c9142c 100644 --- a/tests/index/test_index_document_meilisearch.py +++ b/tests/index/test_index_document_meilisearch.py @@ -759,8 +759,9 @@ def test_update_documents_json_with_skip_creation(empty_index, small_movies_json index.wait_for_task(task.task_uid) # Try to update with new document with skip_creation=True - # update_documents_json expects a string (JSON encoded) - new_doc = json.dumps([documents[1]]) + # update_documents_json accepts bytes (like the fixture) or list of dicts + # Create bytes like the fixture does + new_doc = json.dumps([documents[1]]).encode("utf-8") task = index.update_documents_json(new_doc, skip_creation=True) index.wait_for_task(task.task_uid) From 805d0c326459d512bbd74a7dec6aca3270851500 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 1 Jan 2026 18:55:53 +0500 Subject: [PATCH 6/8] feat: Adds an optional skipCreation. When set to true, documents that don't exist in the index are silently ignored rather than created. --- .code-samples.meilisearch.yaml | 4 ++-- tests/index/test_index_document_meilisearch.py | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index bf7062f3..d683624c 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -42,13 +42,13 @@ add_or_replace_documents_1: |- 'poster': 'https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg', 'overview': 'A boy is given the ability to become an adult superhero in times of need with a single magic word.', 'release_date': '2019-03-23' - }]) + }], skip_creation=True) add_or_update_documents_1: |- client.index('movies').update_documents([{ 'id': 287947, 'title': 'Shazam ⚡️', 'genres': 'comedy' - }]) + }], skip_creation=True) delete_all_documents_1: |- client.index('movies').delete_all_documents() delete_one_document_1: |- diff --git a/tests/index/test_index_document_meilisearch.py b/tests/index/test_index_document_meilisearch.py index 20c9142c..92914f0d 100644 --- a/tests/index/test_index_document_meilisearch.py +++ b/tests/index/test_index_document_meilisearch.py @@ -1,5 +1,6 @@ # pylint: disable=invalid-name +import json from datetime import datetime from json import JSONEncoder from math import ceil @@ -726,8 +727,6 @@ def test_update_documents_in_batches_with_skip_creation(empty_index, small_movie def test_add_documents_json_with_skip_creation(empty_index, small_movies_json_file): """Tests that skip_creation parameter works with add_documents_json.""" - import json - index = empty_index() documents = json.loads(small_movies_json_file.decode("utf-8")) @@ -749,8 +748,6 @@ def test_add_documents_json_with_skip_creation(empty_index, small_movies_json_fi def test_update_documents_json_with_skip_creation(empty_index, small_movies_json_file): """Tests that skip_creation parameter works with update_documents_json.""" - import json - index = empty_index() documents = json.loads(small_movies_json_file.decode("utf-8")) From 80001ac7cffd1ab2185c5e28b48513962f15f8ed Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 1 Jan 2026 19:13:05 +0500 Subject: [PATCH 7/8] feat: Adds an optional skipCreation. When set to true, documents that don't exist in the index are silently ignored rather than created. --- meilisearch/index.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/meilisearch/index.py b/meilisearch/index.py index 5325a125..0b4a5a4d 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -469,7 +469,7 @@ def add_documents( cannot handle, for example UUID and datetime. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -510,7 +510,7 @@ def add_documents_in_batches( cannot handle, for example UUID and datetime. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -556,7 +556,7 @@ def add_documents_json( cannot handle, for example UUID and datetime. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -596,7 +596,7 @@ def add_documents_csv( One ASCII character used to customize the delimiter for CSV. Comma used by default. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -629,7 +629,7 @@ def add_documents_ndjson( The primary-key used in index. Ignored if already set up. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -674,7 +674,7 @@ def add_documents_raw( cannot handle, for example UUID and datetime. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -714,7 +714,7 @@ def update_documents( cannot handle, for example UUID and datetime. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -747,7 +747,7 @@ def update_documents_ndjson( The primary-key used in index. Ignored if already set up skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -785,7 +785,7 @@ def update_documents_json( cannot handle, for example UUID and datetime. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -825,7 +825,7 @@ def update_documents_csv( One ASCII character used to customize the delimiter for CSV. Comma used by default. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -870,7 +870,7 @@ def update_documents_raw( cannot handle, for example UUID and datetime. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- @@ -912,7 +912,7 @@ def update_documents_in_batches( cannot handle, for example UUID and datetime. skip_creation (optional): If True, documents that don't exist in the index are silently ignored rather - than created. Default is False, preserving existing behavior. + than created. If False or None (default), existing behavior is preserved. Returns ------- From 3c22c85dd6062bc8beb1ffe4c870b6d956c4c968 Mon Sep 17 00:00:00 2001 From: awais qureshi Date: Thu, 1 Jan 2026 19:18:07 +0500 Subject: [PATCH 8/8] feat: Adds an optional skipCreation. When set to true, documents that don't exist in the index are silently ignored rather than created. --- tests/index/test_index_document_meilisearch.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/index/test_index_document_meilisearch.py b/tests/index/test_index_document_meilisearch.py index 92914f0d..485d1978 100644 --- a/tests/index/test_index_document_meilisearch.py +++ b/tests/index/test_index_document_meilisearch.py @@ -9,6 +9,7 @@ import pytest +from meilisearch.errors import MeilisearchApiError from meilisearch.models.document import Document from meilisearch.models.task import TaskInfo @@ -207,7 +208,7 @@ def test_get_document_with_fields(index_with_documents): def test_get_document_inexistent(empty_index): """Tests getting one inexistent document from a populated index.""" - with pytest.raises(Exception): + with pytest.raises(MeilisearchApiError): empty_index().get_document("123") @@ -419,7 +420,7 @@ def test_delete_document(index_with_documents): assert isinstance(response, TaskInfo) assert response.task_uid is not None index.wait_for_task(response.task_uid) - with pytest.raises(Exception): + with pytest.raises(MeilisearchApiError): index.get_document("500682") @@ -433,7 +434,7 @@ def test_delete_documents_by_id(index_with_documents): assert response.task_uid is not None index.wait_for_task(response.task_uid) for document in to_delete: - with pytest.raises(Exception): + with pytest.raises(MeilisearchApiError): index.get_document(document) assert "The use of ids is depreciated" in str(w[0].message) @@ -581,8 +582,8 @@ def test_add_documents_with_skip_creation_true(empty_index): task = index.add_documents(new_documents, skip_creation=True) index.wait_for_task(task.task_uid) - # New document should not exist - with pytest.raises(Exception): + # Document "2" should not exist because skip_creation=True prevents creation of new documents + with pytest.raises(MeilisearchApiError): index.get_document("2") # Existing document should still be there @@ -647,8 +648,8 @@ def test_update_documents_with_skip_creation_true(empty_index): task = index.update_documents(new_documents, skip_creation=True) index.wait_for_task(task.task_uid) - # New document should not exist - with pytest.raises(Exception): + # Document "2" should not exist because skip_creation=True prevents creation of new documents + with pytest.raises(MeilisearchApiError): index.get_document("2") # Existing document should still be there and unchanged