diff --git a/meilisearch/client.py b/meilisearch/client.py index 6056c42f..8cf7ff02 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -13,7 +13,8 @@ from meilisearch.config import Config from meilisearch.errors import MeiliSearchError from meilisearch.index import Index -from meilisearch.task import get_task, get_tasks, wait_for_task +from meilisearch.models.task import TaskInfo +from meilisearch.task import cancel_tasks, get_task, get_tasks, wait_for_task class Client: @@ -441,6 +442,27 @@ def get_task(self, uid: int) -> dict[str, Any]: """ return get_task(self.config, uid) + def cancel_tasks(self, parameters: dict[str, Any]) -> TaskInfo: + """Cancel a list of enqueued or processing tasks. + + Parameters + ---------- + parameters (optional): + parameters accepted by the cancel tasks route:https://docs.meilisearch.com/reference/api/tasks.html#cancel-tasks. + + Returns + ------- + task_info: + TaskInfo instance containing information about a task to track the progress of an asynchronous process. + https://docs.meilisearch.com/reference/api/tasks.html#get-one-task + + Raises + ------ + 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 + """ + return cancel_tasks(self.config, parameters=parameters) + def wait_for_task( self, uid: int, diff --git a/meilisearch/models/task.py b/meilisearch/models/task.py index 02572912..c69e84c6 100644 --- a/meilisearch/models/task.py +++ b/meilisearch/models/task.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, Union from camel_converter.pydantic_base import CamelBase @@ -11,7 +11,8 @@ class Task(CamelBase): status: str type: str details: Dict[str, Any] - error: Optional[Dict[str, Any]] + error: Union[Dict[str, Any], None] + canceled_by: Union[int, None] duration: str enqueued_at: str started_at: str diff --git a/meilisearch/task.py b/meilisearch/task.py index 03276481..536cb221 100644 --- a/meilisearch/task.py +++ b/meilisearch/task.py @@ -8,6 +8,7 @@ from meilisearch._httprequests import HttpRequests from meilisearch.config import Config from meilisearch.errors import MeiliSearchTimeoutError +from meilisearch.models.task import TaskInfo def get_tasks( @@ -67,6 +68,37 @@ def get_task(config: Config, uid: int) -> dict[str, Any]: return http.get(f"{config.paths.task}/{uid}") +def cancel_tasks(config: Config, parameters: dict[str, Any]) -> TaskInfo: + """Cancel a list of enqueued or processing tasks. + + Parameters + ---------- + config: + Config object containing permission and location of Meilisearch. + parameters (optional): + parameters accepted by the cancel tasks https://docs.meilisearch.com/reference/api/tasks.html#cancel-task. + + Returns + ------- + task_info: + TaskInfo instance containing information about a task to track the progress of an asynchronous process. + https://docs.meilisearch.com/reference/api/tasks.html#get-one-task + + Raises + ------ + 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 + """ + http = HttpRequests(config) + if parameters is None: + parameters = {} + for param in parameters: + if isinstance(parameters[param], list): + parameters[param] = ",".join(parameters[param]) + response = http.post(f"{config.paths.task}/cancel?{parse.urlencode(parameters)}") + return TaskInfo(**response) + + def wait_for_task( config: Config, uid: int, diff --git a/tests/client/test_client_task_meilisearch.py b/tests/client/test_client_task_meilisearch.py index 0085d8c2..3568807a 100644 --- a/tests/client/test_client_task_meilisearch.py +++ b/tests/client/test_client_task_meilisearch.py @@ -2,6 +2,7 @@ import pytest +from meilisearch.models.task import TaskInfo from tests import common @@ -91,3 +92,42 @@ def test_get_task_inexistent(client): """Tests getting a task that does not exists.""" with pytest.raises(Exception): client.get_task("abc") + + +@pytest.fixture +def create_tasks(empty_index, small_movies): + """Ensures there are some tasks present for testing.""" + index = empty_index() + index.update_ranking_rules(["type", "exactness"]) + index.reset_ranking_rules() + index.add_documents(small_movies) + index.add_documents(small_movies) + + +@pytest.mark.usefixtures("create_tasks") +def test_cancel_tasks(client): + """Tests cancel a task with uid 1.""" + task = client.cancel_tasks({"uids": ["1", "2"]}) + client.wait_for_task(task.task_uid) + tasks = client.get_tasks({"types": "taskCancelation"}) + + assert isinstance(task, TaskInfo) + assert task.task_uid is not None + assert task.index_uid is None + assert task.type == "taskCancelation" + assert "uids" in tasks["results"][0]["details"]["originalFilter"] + assert "uids=1%2C2" in tasks["results"][0]["details"]["originalFilter"] + + +@pytest.mark.usefixtures("create_tasks") +def test_cancel_every_task(client): + """Tests cancel every task.""" + task = client.cancel_tasks({"statuses": ["enqueued", "processing"]}) + client.wait_for_task(task.task_uid) + tasks = client.get_tasks({"types": "taskCancelation"}) + + assert isinstance(task, TaskInfo) + assert task.task_uid is not None + assert task.index_uid is None + assert task.type == "taskCancelation" + assert "statuses=enqueued%2Cprocessing" in tasks["results"][0]["details"]["originalFilter"]