Skip to content

Commit

Permalink
Merge pull request #756 from meilisearch/delete-documents-by-filter
Browse files Browse the repository at this point in the history
Add ability to delete documents by filter
  • Loading branch information
alallema authored May 23, 2023
2 parents 0878fd1 + cc21b64 commit 3041b19
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 19 deletions.
19 changes: 19 additions & 0 deletions meilisearch/errors.py
Original file line number Diff line number Diff line change
@@ -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"""
Expand Down Expand Up @@ -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
39 changes: 31 additions & 8 deletions meilisearch/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
-------
Expand All @@ -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:
Expand Down
2 changes: 0 additions & 2 deletions meilisearch/models/document.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

from typing import Any, Dict, Iterator


Expand Down
21 changes: 20 additions & 1 deletion tests/errors/test_api_error_meilisearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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)
)
33 changes: 25 additions & 8 deletions tests/index/test_index_document_meilisearch.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# pylint: disable=invalid-name

from math import ceil
from warnings import catch_warnings

import pytest

Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit 3041b19

Please sign in to comment.