Skip to content

Commit

Permalink
Merge branch 'main' into meili-bot/bump-version
Browse files Browse the repository at this point in the history
  • Loading branch information
alallema authored Jun 5, 2023
2 parents f548834 + 60b5625 commit 9c14abe
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 27 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
60 changes: 46 additions & 14 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 @@ -301,13 +303,15 @@ def get_document(
)
return Document(document)

@version_error_hint_message
def get_documents(self, parameters: Optional[Dict[str, Any]] = None) -> DocumentsResults:
"""Get a set of documents from the index.
Parameters
----------
parameters (optional):
parameters accepted by the get documents route: https://www.meilisearch.com/docs/reference/api/documents#get-documents
Note: The filter parameter is only available in Meilisearch >= 1.2.0.
Returns
-------
Expand All @@ -323,13 +327,20 @@ def get_documents(self, parameters: Optional[Dict[str, Any]] = None) -> Document
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 parameters is None:
parameters = {}
elif "fields" in parameters and isinstance(parameters["fields"], list):
parameters["fields"] = ",".join(parameters["fields"])
if parameters is None or parameters.get("filter") is None:
if parameters is None:
parameters = {}
elif "fields" in parameters and isinstance(parameters["fields"], list):
parameters["fields"] = ",".join(parameters["fields"])

response = self.http.get(
f"{self.config.paths.index}/{self.uid}/{self.config.paths.document}?{parse.urlencode(parameters)}"
)
return DocumentsResults(response)

response = self.http.get(
f"{self.config.paths.index}/{self.uid}/{self.config.paths.document}?{parse.urlencode(parameters)}"
response = self.http.post(
f"{self.config.paths.index}/{self.uid}/{self.config.paths.document}/fetch",
body=parameters,
)
return DocumentsResults(response)

Expand Down Expand Up @@ -729,13 +740,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 +770,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://www.meilisearch.com/docs/reference/errors/error_codes#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
6 changes: 2 additions & 4 deletions meilisearch/models/document.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from __future__ import annotations

from typing import Any, Dict, Iterator
from typing import Any, Dict, Iterator, List


class Document:
Expand All @@ -22,7 +20,7 @@ def __iter__(self) -> Iterator:

class DocumentsResults:
def __init__(self, resp: Dict[str, Any]) -> None:
self.results: list[Document] = [Document(doc) for doc in resp["results"]]
self.results: List[Document] = [Document(doc) for doc in resp["results"]]
self.offset: int = resp["offset"]
self.limit: int = resp["limit"]
self.total: int = resp["total"]
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)
)
53 changes: 45 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 @@ -102,6 +103,26 @@ def test_get_documents_offset_optional_params(index_with_documents):
assert response_offset_limit.results[0].title == response.results[1].title


def test_get_documents_filter(index_with_documents):
index = index_with_documents()
response = index.update_filterable_attributes(["genre"])
index.wait_for_task(response.task_uid)
response = index.get_documents({"filter": "genre=action"})
genres = {x.genre for x in response.results}
assert len(genres) == 1
assert next(iter(genres)) == "action"


def test_get_documents_filter_with_fields(index_with_documents):
index = index_with_documents()
response = index.update_filterable_attributes(["genre"])
index.wait_for_task(response.task_uid)
response = index.get_documents({"fields": ["genre"], "filter": "genre=action"})
genres = {x.genre for x in response.results}
assert len(genres) == 1
assert next(iter(genres)) == "action"


def test_update_documents(index_with_documents, small_movies):
"""Tests updating a single document and a set of documents."""
index = index_with_documents()
Expand Down Expand Up @@ -160,17 +181,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 9c14abe

Please sign in to comment.