diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 472b1384..0ccfd703 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -253,9 +253,9 @@ update_sortable_attributes_1: |- reset_sortable_attributes_1: |- client.index('books').reset_sortable_attributes() get_index_stats_1: |- - client.index('movies').get_stats() + client.index('movies').get_stats(show_internal_database_sizes=True, size_format='human') get_indexes_stats_1: |- - client.get_all_stats() + client.get_all_stats(show_internal_database_sizes=True, size_format='human') get_health_1: |- client.health() get_version_1: |- diff --git a/meilisearch/client.py b/meilisearch/client.py index bd0ec8fa..4f22fa02 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -30,6 +30,7 @@ MeilisearchError, ) from meilisearch.index import Index +from meilisearch.models.index import SizeFormat from meilisearch.models.key import Key, KeysResults from meilisearch.models.task import Batch, BatchResults, Task, TaskInfo, TaskResults from meilisearch.models.webhook import Webhook, WebhooksResults @@ -322,12 +323,26 @@ def update_documents_by_function( body=dict(queries), ) - def get_all_stats(self) -> Dict[str, Any]: + def get_all_stats( + self, + *, + show_internal_database_sizes: Optional[bool] = None, + size_format: Optional[Union[SizeFormat, str]] = None, + ) -> Dict[str, Any]: """Get all stats of Meilisearch Get information about database size and all indexes https://www.meilisearch.com/docs/reference/api/stats + Parameters + ---------- + show_internal_database_sizes (optional): + When true, index stat objects contain an additional internalDatabaseSizes key + with the size of each internal database. Defaults to false. + size_format (optional): + When set to "human", database sizes are returned as strings with units (e.g. "1.5 GiB"). + When set to "raw" or omitted, sizes are returned as numbers in bytes. + Returns ------- stats: @@ -338,7 +353,18 @@ def get_all_stats(self) -> Dict[str, Any]: 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.http.get(self.config.paths.stat) + params: Dict[str, Any] = {} + if show_internal_database_sizes is not None: + params["showInternalDatabaseSizes"] = str(show_internal_database_sizes).lower() + if size_format is not None: + params["sizeFormat"] = ( + size_format.value if isinstance(size_format, SizeFormat) else size_format + ) + + path = self.config.paths.stat + if params: + path = f"{path}?{parse.urlencode(params)}" + return self.http.get(path) def health(self) -> Dict[str, str]: """Get health of the Meilisearch server. diff --git a/meilisearch/index.py b/meilisearch/index.py index 6c65874a..2962a49a 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -41,6 +41,7 @@ Pagination, PrefixSearch, ProximityPrecision, + SizeFormat, TypoTolerance, ) from meilisearch.models.task import Task, TaskInfo, TaskResults @@ -313,12 +314,26 @@ def wait_for_task( """ return self.task_handler.wait_for_task(uid, timeout_in_ms, interval_in_ms) - def get_stats(self) -> IndexStats: + def get_stats( + self, + *, + show_internal_database_sizes: Optional[bool] = None, + size_format: Optional[Union[SizeFormat, str]] = None, + ) -> IndexStats: """Get stats of the index. Get information about the number of documents, field frequencies, ... https://www.meilisearch.com/docs/reference/api/stats + Parameters + ---------- + show_internal_database_sizes (optional): + When true, the response contains an additional internalDatabaseSizes key + with the size of each internal database. Defaults to false. + size_format (optional): + When set to "human", database sizes are returned as strings with units (e.g. "1.5 GiB"). + When set to "raw" or omitted, sizes are returned as numbers in bytes. + Returns ------- stats: @@ -329,7 +344,18 @@ def get_stats(self) -> IndexStats: 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 """ - stats = self.http.get(f"{self.config.paths.index}/{self.uid}/{self.config.paths.stat}") + params: Dict[str, Any] = {} + if show_internal_database_sizes is not None: + params["showInternalDatabaseSizes"] = str(show_internal_database_sizes).lower() + if size_format is not None: + params["sizeFormat"] = ( + size_format.value if isinstance(size_format, SizeFormat) else size_format + ) + + path = f"{self.config.paths.index}/{self.uid}/{self.config.paths.stat}" + if params: + path = f"{path}?{parse.urlencode(params)}" + stats = self.http.get(path) return IndexStats(**stats) @version_error_hint_message diff --git a/meilisearch/models/index.py b/meilisearch/models/index.py index bd60f848..9796e20a 100644 --- a/meilisearch/models/index.py +++ b/meilisearch/models/index.py @@ -24,12 +24,18 @@ def __iter__(self) -> Iterator: return iter(self.__dict__.items()) +class SizeFormat(str, Enum): + RAW = "raw" + HUMAN = "human" + + class IndexStats(CamelBase): model_config = ConfigDict(arbitrary_types_allowed=True) number_of_documents: int is_indexing: bool field_distribution: FieldDistribution + internal_database_sizes: Optional[Dict[str, Any]] = None @field_validator("field_distribution", mode="before") @classmethod diff --git a/tests/client/test_client_stats_meilisearch.py b/tests/client/test_client_stats_meilisearch.py index 52a0eebf..8c016197 100644 --- a/tests/client/test_client_stats_meilisearch.py +++ b/tests/client/test_client_stats_meilisearch.py @@ -1,5 +1,11 @@ +import re + import pytest +from meilisearch.models.index import SizeFormat + +HUMAN_SIZE_PATTERN = re.compile(r"^\d+(\.\d+)?\s+(B|KiB|MiB|GiB|TiB)$") + @pytest.mark.usefixtures("indexes_sample") def test_get_all_stats(client): @@ -12,3 +18,53 @@ def test_get_all_stats(client): assert "indexes" in response assert "indexUID" in response["indexes"] assert "indexUID2" in response["indexes"] + + +@pytest.mark.usefixtures("indexes_sample") +def test_get_all_stats_with_internal_database_sizes(client): + """Tests getting all stats with showInternalDatabaseSizes parameter.""" + response = client.get_all_stats(show_internal_database_sizes=True) + assert isinstance(response, dict) + assert isinstance(response["databaseSize"], int) + assert any("internalDatabaseSizes" in index_stats for index_stats in response["indexes"].values()) + for index_stats in response["indexes"].values(): + if "internalDatabaseSizes" in index_stats: + assert isinstance(index_stats["internalDatabaseSizes"], dict) + assert len(index_stats["internalDatabaseSizes"]) > 0 + assert all( + isinstance(value, int) for value in index_stats["internalDatabaseSizes"].values() + ) + + +@pytest.mark.usefixtures("indexes_sample") +def test_get_all_stats_with_size_format(client): + """Tests getting all stats with sizeFormat parameter.""" + response = client.get_all_stats( + show_internal_database_sizes=True, + size_format=SizeFormat.HUMAN, + ) + assert isinstance(response, dict) + assert isinstance(response["databaseSize"], str) + assert HUMAN_SIZE_PATTERN.match(response["databaseSize"]) + assert any( + "internalDatabaseSizes" in index_stats for index_stats in response["indexes"].values() + ) + for index_stats in response["indexes"].values(): + if "internalDatabaseSizes" in index_stats: + assert all( + isinstance(value, str) and HUMAN_SIZE_PATTERN.match(value) + for value in index_stats["internalDatabaseSizes"].values() + ) + + +@pytest.mark.usefixtures("indexes_sample") +def test_get_all_stats_with_all_params(client): + """Tests getting all stats with both query parameters.""" + response = client.get_all_stats( + show_internal_database_sizes=True, + size_format="human", + ) + assert isinstance(response, dict) + assert isinstance(response["databaseSize"], str) + assert "indexes" in response + assert any("internalDatabaseSizes" in index_stats for index_stats in response["indexes"].values()) diff --git a/tests/index/test_index_stats_meilisearch.py b/tests/index/test_index_stats_meilisearch.py index 9af1a7e9..2ed86b73 100644 --- a/tests/index/test_index_stats_meilisearch.py +++ b/tests/index/test_index_stats_meilisearch.py @@ -1,4 +1,8 @@ -from meilisearch.models.index import IndexStats +import re + +from meilisearch.models.index import IndexStats, SizeFormat + +HUMAN_SIZE_PATTERN = re.compile(r"^\d+(\.\d+)?\s+(B|KiB|MiB|GiB|TiB)$") def test_get_stats(empty_index): @@ -15,3 +19,39 @@ def test_get_stats_default(index_with_documents): assert response.number_of_documents == 31 assert hasattr(response.field_distribution, "genre") assert response.field_distribution.genre == 11 + + +def test_get_stats_with_internal_database_sizes(index_with_documents): + """Tests getting stats with showInternalDatabaseSizes parameter.""" + response = index_with_documents().get_stats(show_internal_database_sizes=True) + assert isinstance(response, IndexStats) + assert response.internal_database_sizes is not None + assert isinstance(response.internal_database_sizes, dict) + assert len(response.internal_database_sizes) > 0 + assert all(isinstance(value, int) for value in response.internal_database_sizes.values()) + + +def test_get_stats_with_size_format(index_with_documents): + """Tests getting stats with sizeFormat parameter.""" + response = index_with_documents().get_stats( + show_internal_database_sizes=True, + size_format=SizeFormat.HUMAN, + ) + assert isinstance(response, IndexStats) + assert response.internal_database_sizes is not None + assert all( + isinstance(value, str) and HUMAN_SIZE_PATTERN.match(value) + for value in response.internal_database_sizes.values() + ) + + +def test_get_stats_with_all_params(index_with_documents): + """Tests getting stats with both query parameters.""" + response = index_with_documents().get_stats( + show_internal_database_sizes=True, + size_format="human", + ) + assert isinstance(response, IndexStats) + assert response.number_of_documents == 31 + assert response.internal_database_sizes is not None + assert all(isinstance(value, str) for value in response.internal_database_sizes.values())