From 00b778c94187cf4bc5ecfd9483fc05cbe83cfb6e Mon Sep 17 00:00:00 2001 From: ellnix Date: Fri, 17 Jan 2025 15:04:11 +0100 Subject: [PATCH 1/3] Support new batches API --- meilisearch/client.py | 42 ++++++++++++++++- meilisearch/config.py | 1 + meilisearch/models/task.py | 43 +++++++++++++++++ meilisearch/task.py | 49 +++++++++++++++++++- tests/client/test_client_task_meilisearch.py | 30 ++++++++++++ 5 files changed, 163 insertions(+), 2 deletions(-) diff --git a/meilisearch/client.py b/meilisearch/client.py index 981cb2c1..c635a575 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -16,7 +16,7 @@ from meilisearch.errors import MeilisearchError from meilisearch.index import Index from meilisearch.models.key import Key, KeysResults -from meilisearch.models.task import Task, TaskInfo, TaskResults +from meilisearch.models.task import Batch, BatchResults, Task, TaskInfo, TaskResults from meilisearch.task import TaskHandler @@ -611,6 +611,46 @@ def wait_for_task( """ return self.task_handler.wait_for_task(uid, timeout_in_ms, interval_in_ms) + def get_batches(self, parameters: Optional[MutableMapping[str, Any]] = None) -> BatchResults: + """Get all batches. + + Parameters + ---------- + parameters (optional): + parameters accepted by the get batches route: https://www.meilisearch.com/docs/reference/api/batches#get-batches. + + Returns + ------- + batch: + BatchResult instance containing limit, from, next and results containing a list of all batches. + + 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 self.task_handler.get_batches(parameters=parameters) + + def get_batch(self, uid: int) -> Batch: + """Get one tasks batch. + + Parameters + ---------- + uid: + Identifier of the batch. + + Returns + ------- + batch: + Batch instance containing information about the progress of the asynchronous batch. + + 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 self.task_handler.get_batch(uid) + def generate_tenant_token( self, api_key_uid: str, diff --git a/meilisearch/config.py b/meilisearch/config.py index 9b5206eb..624be11e 100644 --- a/meilisearch/config.py +++ b/meilisearch/config.py @@ -14,6 +14,7 @@ class Paths: version = "version" index = "indexes" task = "tasks" + batch = "batches" stat = "stats" search = "search" facet_search = "facet-search" diff --git a/meilisearch/models/task.py b/meilisearch/models/task.py index a622e1ee..fb01db12 100644 --- a/meilisearch/models/task.py +++ b/meilisearch/models/task.py @@ -113,3 +113,46 @@ def __init__(self, resp: Dict[str, Any]) -> None: self.total: int = resp["total"] self.from_: int = resp["from"] self.next_: int = resp["next"] + + +class Batch(CamelBase): + uid: int + details: Optional[Dict[str, Any]] = None + stats: Optional[Dict[str, Union[int, Dict[str, Any]]]] = None + duration: Optional[str] = None + started_at: Optional[datetime] = None + finished_at: Optional[datetime] = None + progress: Optional[Dict[str, Union[float, List[Dict[str, Any]]]]] = None + + if is_pydantic_2(): + + @pydantic.field_validator("started_at", mode="before") # type: ignore[attr-defined] + @classmethod + def validate_started_at(cls, v: str) -> Optional[datetime]: # pylint: disable=invalid-name + return iso_to_date_time(v) + + @pydantic.field_validator("finished_at", mode="before") # type: ignore[attr-defined] + @classmethod + def validate_finished_at(cls, v: str) -> Optional[datetime]: # pylint: disable=invalid-name + return iso_to_date_time(v) + + else: # pragma: no cover + + @pydantic.validator("started_at", pre=True) + @classmethod + def validate_started_at(cls, v: str) -> Optional[datetime]: # pylint: disable=invalid-name + return iso_to_date_time(v) + + @pydantic.validator("finished_at", pre=True) + @classmethod + def validate_finished_at(cls, v: str) -> Optional[datetime]: # pylint: disable=invalid-name + return iso_to_date_time(v) + + +class BatchResults: + def __init__(self, resp: Dict[str, Any]) -> None: + self.results: List[Batch] = [Batch(**batch) for batch in resp["results"]] + self.total: int = resp["total"] + self.limit: int = resp["limit"] + self.from_: int = resp["from"] + self.next_: int = resp["next"] diff --git a/meilisearch/task.py b/meilisearch/task.py index 03f332ea..165a836b 100644 --- a/meilisearch/task.py +++ b/meilisearch/task.py @@ -8,7 +8,7 @@ from meilisearch._httprequests import HttpRequests from meilisearch.config import Config from meilisearch.errors import MeilisearchTimeoutError -from meilisearch.models.task import Task, TaskInfo, TaskResults +from meilisearch.models.task import Batch, BatchResults, Task, TaskInfo, TaskResults class TaskHandler: @@ -27,6 +27,53 @@ def __init__(self, config: Config): self.config = config self.http = HttpRequests(config) + def get_batches(self, parameters: Optional[MutableMapping[str, Any]] = None) -> BatchResults: + """Get all task batches. + + Parameters + ---------- + parameters (optional): + parameters accepted by the get batches route: https://www.meilisearch.com/docs/reference/api/batches#get-batches. + + Returns + ------- + batch: + BatchResults instance contining limit, from, next and results containing a list of all batches. + + 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 + """ + if parameters is None: + parameters = {} + for param in parameters: + if isinstance(parameters[param], list): + parameters[param] = ",".join(parameters[param]) + batches = self.http.get(f"{self.config.paths.batch}?{parse.urlencode(parameters)}") + return BatchResults(batches) + + def get_batch(self, uid: int) -> Batch: + """Get one tasks batch. + + Parameters + ---------- + uid: + Identifier of the batch. + + Returns + ------- + task: + Batch instance containing information about the progress of the asynchronous batch. + + 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 + """ + batch = self.http.get(f"{self.config.paths.batch}/{uid}") + return Batch(**batch) + def get_tasks(self, parameters: Optional[MutableMapping[str, Any]] = None) -> TaskResults: """Get all tasks. diff --git a/tests/client/test_client_task_meilisearch.py b/tests/client/test_client_task_meilisearch.py index c9a9b8a5..9484b1a9 100644 --- a/tests/client/test_client_task_meilisearch.py +++ b/tests/client/test_client_task_meilisearch.py @@ -165,3 +165,33 @@ def test_get_tasks_in_reverse(client): reverse_tasks = client.get_tasks({"reverse": "true"}) assert reverse_tasks.results[0] == tasks.results[-1] + + +def test_get_batches_default(client): + """Tests getting the batches.""" + batches = client.get_batches() + assert len(batches.results) >= 1 + + +@pytest.mark.usefixtures("create_tasks") +def test_get_batches_with_parameters(client): + """Tests getting batches with a parameter (empty or otherwise).""" + rev_batches = client.get_batches({"reverse": "true"}) + batches = client.get_batches({}) + + assert len(batches.results) > 1 + assert rev_batches.results[0].uid == batches.results[-1].uid + + +def test_get_batch(client): + """Tests getting the details of a batch.""" + batches = client.get_batches({"limit": 1}) + batch = client.get_batch(batches.results[0].uid) + batch_dict = batch.__dict__ + assert "uid" in batch_dict + assert "details" in batch_dict + assert "stats" in batch_dict + assert "duration" in batch_dict + assert "started_at" in batch_dict + assert "finished_at" in batch_dict + assert "progress" in batch_dict From fa1594b6733955ababcfe21c804e5f6dbc085afa Mon Sep 17 00:00:00 2001 From: ellnix Date: Fri, 17 Jan 2025 15:08:58 +0100 Subject: [PATCH 2/3] Add batches code-samples --- .code-samples.meilisearch.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index afa1c129..fff94476 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -747,3 +747,7 @@ multi_search_federated_1: |- [{"indexUid": "movies", "q": "batman"}, {"indexUid": "comics", "q": "batman"}], {} ) +get_all_batches_1: |- + client.get_batches() +get_batch_1: |- + client.get_batch(BATCH_UID) From a7051c7ddca7fc3fa2255420111e774332348c01 Mon Sep 17 00:00:00 2001 From: ellnix Date: Thu, 23 Jan 2025 18:13:56 +0100 Subject: [PATCH 3/3] Refactor batch classes and tests Co-authored-by: sanders41 --- meilisearch/models/task.py | 14 +++++++------- meilisearch/task.py | 4 ++-- tests/client/test_client_task_meilisearch.py | 12 +++--------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/meilisearch/models/task.py b/meilisearch/models/task.py index fb01db12..b8f0d68e 100644 --- a/meilisearch/models/task.py +++ b/meilisearch/models/task.py @@ -149,10 +149,10 @@ def validate_finished_at(cls, v: str) -> Optional[datetime]: # pylint: disable= return iso_to_date_time(v) -class BatchResults: - def __init__(self, resp: Dict[str, Any]) -> None: - self.results: List[Batch] = [Batch(**batch) for batch in resp["results"]] - self.total: int = resp["total"] - self.limit: int = resp["limit"] - self.from_: int = resp["from"] - self.next_: int = resp["next"] +class BatchResults(CamelBase): + results: List[Batch] + total: int + limit: int + from_: int + # None means last page + next_: Optional[int] diff --git a/meilisearch/task.py b/meilisearch/task.py index 165a836b..22900fbb 100644 --- a/meilisearch/task.py +++ b/meilisearch/task.py @@ -48,10 +48,10 @@ def get_batches(self, parameters: Optional[MutableMapping[str, Any]] = None) -> if parameters is None: parameters = {} for param in parameters: - if isinstance(parameters[param], list): + if isinstance(parameters[param], (list, tuple)): parameters[param] = ",".join(parameters[param]) batches = self.http.get(f"{self.config.paths.batch}?{parse.urlencode(parameters)}") - return BatchResults(batches) + return BatchResults(**batches) def get_batch(self, uid: int) -> Batch: """Get one tasks batch. diff --git a/tests/client/test_client_task_meilisearch.py b/tests/client/test_client_task_meilisearch.py index 9484b1a9..1221a2d1 100644 --- a/tests/client/test_client_task_meilisearch.py +++ b/tests/client/test_client_task_meilisearch.py @@ -186,12 +186,6 @@ def test_get_batches_with_parameters(client): def test_get_batch(client): """Tests getting the details of a batch.""" batches = client.get_batches({"limit": 1}) - batch = client.get_batch(batches.results[0].uid) - batch_dict = batch.__dict__ - assert "uid" in batch_dict - assert "details" in batch_dict - assert "stats" in batch_dict - assert "duration" in batch_dict - assert "started_at" in batch_dict - assert "finished_at" in batch_dict - assert "progress" in batch_dict + uid = batches.results[0].uid + batch = client.get_batch(uid) + assert batch.uid == uid