diff --git a/meilisearch/errors.py b/meilisearch/errors.py index 0da4e654..2330a250 100644 --- a/meilisearch/errors.py +++ b/meilisearch/errors.py @@ -1,9 +1,16 @@ from __future__ import annotations import json +from functools import wraps +from typing import TYPE_CHECKING, Any, Callable from requests import Response +if TYPE_CHECKING: + from meilisearch.client import Client + from meilisearch.index import Index + from meilisearch.task import TaskHandler + class MeilisearchError(Exception): """Generic class for Meilisearch error handling""" @@ -54,3 +61,15 @@ class MeilisearchTimeoutError(MeilisearchError): def __str__(self) -> str: return f"MeilisearchTimeoutError, {self.message}" + + +def version_error_hint_message(func: Callable) -> Any: + @wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> Any: + try: + return func(*args, **kwargs) + except MeilisearchApiError as exc: + exc.message = f"{exc.message}. Hint: It might not be working because you're not up to date with the Meilisearch version that {func.__name__} call requires." + raise exc + + return wrapper diff --git a/meilisearch/index.py b/meilisearch/index.py index 16dfb74b..b26e04a2 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -3,9 +3,11 @@ from datetime import datetime from typing import Any, Dict, Generator, List, Optional, Union from urllib import parse +from warnings import warn from meilisearch._httprequests import HttpRequests from meilisearch.config import Config +from meilisearch.errors import version_error_hint_message from meilisearch.models.document import Document, DocumentsResults from meilisearch.models.index import Faceting, IndexStats, Pagination, TypoTolerance from meilisearch.models.task import Task, TaskInfo, TaskResults @@ -729,13 +731,24 @@ def delete_document(self, document_id: Union[str, int]) -> TaskInfo: ) return TaskInfo(**response) - def delete_documents(self, ids: List[Union[str, int]]) -> TaskInfo: - """Delete multiple documents from the index. + @version_error_hint_message + def delete_documents( + self, + ids: Optional[List[Union[str, int]]] = None, + *, + filter: Optional[ # pylint: disable=redefined-builtin + Union[str, List[Union[str, List[str]]]] + ] = None, + ) -> TaskInfo: + """Delete multiple documents from the index by id or filter. Parameters ---------- - list: - List of unique identifiers of documents. + ids: + List of unique identifiers of documents. Note: using ids is depreciated and will be + removed in a future version. + filter: + The filter value information. Returns ------- @@ -748,10 +761,20 @@ def delete_documents(self, ids: List[Union[str, int]]) -> TaskInfo: MeilisearchApiError An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://docs.meilisearch.com/errors/#meilisearch-errors """ - response = self.http.post( - f"{self.config.paths.index}/{self.uid}/{self.config.paths.document}/delete-batch", - [str(i) for i in ids], - ) + if ids: + warn( + "The use of ids is depreciated and will be removed in the future", + DeprecationWarning, + ) + response = self.http.post( + f"{self.config.paths.index}/{self.uid}/{self.config.paths.document}/delete-batch", + [str(i) for i in ids], + ) + else: + response = self.http.post( + f"{self.config.paths.index}/{self.uid}/{self.config.paths.document}/delete", + body={"filter": filter}, + ) return TaskInfo(**response) def delete_all_documents(self) -> TaskInfo: diff --git a/meilisearch/models/document.py b/meilisearch/models/document.py index f68247a2..f7c621d8 100644 --- a/meilisearch/models/document.py +++ b/meilisearch/models/document.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Any, Dict, Iterator diff --git a/tests/errors/test_api_error_meilisearch.py b/tests/errors/test_api_error_meilisearch.py index f9a4064c..5394f717 100644 --- a/tests/errors/test_api_error_meilisearch.py +++ b/tests/errors/test_api_error_meilisearch.py @@ -6,7 +6,7 @@ import requests import meilisearch -from meilisearch.errors import MeilisearchApiError +from meilisearch.errors import MeilisearchApiError, version_error_hint_message from tests import BASE_URL, MASTER_KEY @@ -33,3 +33,22 @@ def test_meilisearch_api_error_no_code(mock_post): with pytest.raises(MeilisearchApiError): client = meilisearch.Client(BASE_URL, MASTER_KEY + "123") client.create_index("some_index") + + +def test_version_error_hint_message(): + mock_response = requests.models.Response() + mock_response.status_code = 408 + + class FakeClass: + @version_error_hint_message + def test_method(self): + raise MeilisearchApiError("This is a test", mock_response) + + with pytest.raises(MeilisearchApiError) as e: + fake = FakeClass() + fake.test_method() + + assert ( + "MeilisearchApiError. This is a test. Hint: It might not be working because you're not up to date with the Meilisearch version that test_method call requires." + == str(e.value) + ) diff --git a/tests/index/test_index_document_meilisearch.py b/tests/index/test_index_document_meilisearch.py index 086326b1..cb92fec7 100644 --- a/tests/index/test_index_document_meilisearch.py +++ b/tests/index/test_index_document_meilisearch.py @@ -1,6 +1,7 @@ # pylint: disable=invalid-name from math import ceil +from warnings import catch_warnings import pytest @@ -161,17 +162,33 @@ def test_delete_document(index_with_documents): index.get_document("500682") -def test_delete_documents(index_with_documents): +def test_delete_documents_by_id(index_with_documents): """Tests deleting a set of documents.""" - to_delete = [522681, "450465", 329996] + with catch_warnings(record=True) as w: + to_delete = [522681, "450465", 329996] + index = index_with_documents() + response = index.delete_documents(to_delete) + assert isinstance(response, TaskInfo) + assert response.task_uid is not None + index.wait_for_task(response.task_uid) + for document in to_delete: + with pytest.raises(Exception): + index.get_document(document) + assert "The use of ids is depreciated" in str(w[0].message) + + +def test_delete_documents(index_with_documents): index = index_with_documents() - response = index.delete_documents(to_delete) - assert isinstance(response, TaskInfo) - assert response.task_uid is not None + response = index.update_filterable_attributes(["genre"]) + index.wait_for_task(response.task_uid) + response = index.get_documents() + assert "action" in ([x.__dict__.get("genre") for x in response.results]) + response = index.delete_documents(filter="genre=action") index.wait_for_task(response.task_uid) - for document in to_delete: - with pytest.raises(Exception): - index.get_document(document) + response = index.get_documents() + genres = [x.__dict__.get("genre") for x in response.results] + assert "action" not in genres + assert "cartoon" in genres def test_delete_all_documents(index_with_documents):