From 919ad72a7b2897ac1af3da16385564b867092b89 Mon Sep 17 00:00:00 2001 From: ellnix Date: Fri, 24 Jan 2025 15:16:13 +0100 Subject: [PATCH 1/4] Replace task model with CamelBase --- meilisearch/models/task.py | 13 ++++++------- meilisearch/task.py | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/meilisearch/models/task.py b/meilisearch/models/task.py index b8f0d68e..2b65c509 100644 --- a/meilisearch/models/task.py +++ b/meilisearch/models/task.py @@ -106,13 +106,12 @@ def validate_enqueued_at(cls, v: str) -> datetime: # pylint: disable=invalid-na return converted -class TaskResults: - def __init__(self, resp: Dict[str, Any]) -> None: - self.results: List[Task] = [Task(**task) for task in resp["results"]] - self.limit: int = resp["limit"] - self.total: int = resp["total"] - self.from_: int = resp["from"] - self.next_: int = resp["next"] +class TaskResults(CamelBase): + results: List[Task] + limit: int + total: int + from_: int + next_: Optional[int] class Batch(CamelBase): diff --git a/meilisearch/task.py b/meilisearch/task.py index 22900fbb..0ac8a3eb 100644 --- a/meilisearch/task.py +++ b/meilisearch/task.py @@ -99,7 +99,7 @@ def get_tasks(self, parameters: Optional[MutableMapping[str, Any]] = None) -> Ta if isinstance(parameters[param], list): parameters[param] = ",".join(parameters[param]) tasks = self.http.get(f"{self.config.paths.task}?{parse.urlencode(parameters)}") - return TaskResults(tasks) + return TaskResults(**tasks) def get_task(self, uid: int) -> Task: """Get one task. From cab011beb4c510208da87206e785db949228a86f Mon Sep 17 00:00:00 2001 From: ellnix Date: Fri, 24 Jan 2025 15:20:34 +0100 Subject: [PATCH 2/4] Allow tuples in addition to lists in params --- meilisearch/index.py | 2 +- meilisearch/task.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/meilisearch/index.py b/meilisearch/index.py index 7504e722..21f82581 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -364,7 +364,7 @@ def get_document( """ if parameters is None: parameters = {} - elif "fields" in parameters and isinstance(parameters["fields"], list): + elif "fields" in parameters and isinstance(parameters["fields"], (list, tuple)): parameters["fields"] = ",".join(parameters["fields"]) document = self.http.get( diff --git a/meilisearch/task.py b/meilisearch/task.py index 0ac8a3eb..e492bd41 100644 --- a/meilisearch/task.py +++ b/meilisearch/task.py @@ -96,7 +96,7 @@ def get_tasks(self, parameters: Optional[MutableMapping[str, Any]] = None) -> Ta 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]) tasks = self.http.get(f"{self.config.paths.task}?{parse.urlencode(parameters)}") return TaskResults(**tasks) @@ -142,7 +142,7 @@ def cancel_tasks(self, parameters: MutableMapping[str, Any]) -> TaskInfo: 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 """ for param in parameters: - if isinstance(parameters[param], list): + if isinstance(parameters[param], (list, tuple)): parameters[param] = ",".join(parameters[param]) response = self.http.post(f"{self.config.paths.task}/cancel?{parse.urlencode(parameters)}") return TaskInfo(**response) @@ -166,7 +166,7 @@ def delete_tasks(self, parameters: MutableMapping[str, Any]) -> TaskInfo: 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 """ for param in parameters: - if isinstance(parameters[param], list): + if isinstance(parameters[param], (list, tuple)): parameters[param] = ",".join(parameters[param]) response = self.http.delete(f"{self.config.paths.task}?{parse.urlencode(parameters)}") return TaskInfo(**response) From 28a09273ed30eeb6c7d2524e30f6b06ee9711a4c Mon Sep 17 00:00:00 2001 From: ellnix Date: Fri, 24 Jan 2025 16:23:50 +0100 Subject: [PATCH 3/4] Replace IndexStats with CamelBase class --- meilisearch/index.py | 2 +- meilisearch/models/index.py | 26 ++++---------------- tests/index/test_index_stats_meilisearch.py | 4 +-- tests/models/test_index.py | 27 --------------------- 4 files changed, 8 insertions(+), 51 deletions(-) delete mode 100644 tests/models/test_index.py diff --git a/meilisearch/index.py b/meilisearch/index.py index 21f82581..4fc43eeb 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -269,7 +269,7 @@ def get_stats(self) -> IndexStats: 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 """ stats = self.http.get(f"{self.config.paths.index}/{self.uid}/{self.config.paths.stat}") - return IndexStats(stats) + return IndexStats(**stats) @version_error_hint_message def search(self, query: str, opt_params: Optional[Mapping[str, Any]] = None) -> Dict[str, Any]: diff --git a/meilisearch/models/index.py b/meilisearch/models/index.py index bee521ed..e08cfb4d 100644 --- a/meilisearch/models/index.py +++ b/meilisearch/models/index.py @@ -1,31 +1,15 @@ from __future__ import annotations from enum import Enum -from typing import Any, Dict, Iterator, List, Optional, Union +from typing import Dict, List, Optional, Union -from camel_converter import to_snake from camel_converter.pydantic_base import CamelBase -class IndexStats: - __dict: Dict - - def __init__(self, doc: Dict[str, Any]) -> None: - self.__dict = doc - for key, val in doc.items(): - key = to_snake(key) - if isinstance(val, dict): - setattr(self, key, IndexStats(val)) - else: - setattr(self, key, val) - - def __getattr__(self, attr: str) -> Any: - if attr in self.__dict.keys(): - return attr - raise AttributeError(f"{self.__class__.__name__} object has no attribute {attr}") - - def __iter__(self) -> Iterator: - return iter(self.__dict__.items()) +class IndexStats(CamelBase): + number_of_documents: int + is_indexing: bool + field_distribution: Dict[str, int] class Faceting(CamelBase): diff --git a/tests/index/test_index_stats_meilisearch.py b/tests/index/test_index_stats_meilisearch.py index 9af1a7e9..3226fa09 100644 --- a/tests/index/test_index_stats_meilisearch.py +++ b/tests/index/test_index_stats_meilisearch.py @@ -13,5 +13,5 @@ def test_get_stats_default(index_with_documents): response = index_with_documents().get_stats() assert isinstance(response, IndexStats) assert response.number_of_documents == 31 - assert hasattr(response.field_distribution, "genre") - assert response.field_distribution.genre == 11 + assert "genre" in response.field_distribution + assert response.field_distribution["genre"] == 11 diff --git a/tests/models/test_index.py b/tests/models/test_index.py deleted file mode 100644 index d3702e51..00000000 --- a/tests/models/test_index.py +++ /dev/null @@ -1,27 +0,0 @@ -# pylint: disable=unnecessary-dunder-call - -import pytest - -from meilisearch.models.index import IndexStats - - -def test_getattr(): - document = IndexStats({"field1": "test 1", "fiels2": "test 2"}) - assert document.__getattr__("field1") == "field1" - - -def test_getattr_not_found(): - document = IndexStats({"field1": "test 1", "fiels2": "test 2"}) - with pytest.raises(AttributeError): - document.__getattr__("bad") - - -def test_iter(): - # I wrote a test what what this does, but I have a feeling this isn't actually what it was - # expected to do when written as it doesn't really act like I would expect an iterator to act. - document = IndexStats({"field1": "test 1", "fiels2": "test 2"}) - - assert next(document.__iter__()) == ( - "_IndexStats__dict", - {"field1": "test 1", "fiels2": "test 2"}, - ) From ef069de576ad85b12b4e84147484daaf611ee7fe Mon Sep 17 00:00:00 2001 From: ellnix Date: Fri, 24 Jan 2025 20:08:33 +0100 Subject: [PATCH 4/4] Keep compat with old IndexStats.field_distribution --- meilisearch/models/index.py | 32 +++++++++++++++++++-- tests/index/test_index_stats_meilisearch.py | 4 +-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/meilisearch/models/index.py b/meilisearch/models/index.py index e08cfb4d..19f807fb 100644 --- a/meilisearch/models/index.py +++ b/meilisearch/models/index.py @@ -1,15 +1,43 @@ from __future__ import annotations from enum import Enum -from typing import Dict, List, Optional, Union +from typing import Any, Dict, Iterator, List, Optional, Union from camel_converter.pydantic_base import CamelBase +from pydantic import ConfigDict, field_validator + + +class FieldDistribution: + __dict: Dict + + def __init__(self, dist: Dict[str, int]) -> None: + self.__dict = dist + for key in dist: + setattr(self, key, dist[key]) + + def __getattr__(self, attr: str) -> str: + if attr in self.__dict.keys(): + return attr + raise AttributeError(f"{self.__class__.__name__} object has no attribute {attr}") + + def __iter__(self) -> Iterator: + return iter(self.__dict__.items()) class IndexStats(CamelBase): + model_config = ConfigDict(arbitrary_types_allowed=True) + number_of_documents: int is_indexing: bool - field_distribution: Dict[str, int] + field_distribution: FieldDistribution + + @field_validator("field_distribution", mode="before") + @classmethod + def build_field_distribution(cls, v: Any) -> FieldDistribution: + if not isinstance(v, dict): + raise TypeError('"field_distribution" in IndexStats must be a dict') + + return FieldDistribution(v) class Faceting(CamelBase): diff --git a/tests/index/test_index_stats_meilisearch.py b/tests/index/test_index_stats_meilisearch.py index 3226fa09..9af1a7e9 100644 --- a/tests/index/test_index_stats_meilisearch.py +++ b/tests/index/test_index_stats_meilisearch.py @@ -13,5 +13,5 @@ def test_get_stats_default(index_with_documents): response = index_with_documents().get_stats() assert isinstance(response, IndexStats) assert response.number_of_documents == 31 - assert "genre" in response.field_distribution - assert response.field_distribution["genre"] == 11 + assert hasattr(response.field_distribution, "genre") + assert response.field_distribution.genre == 11