Skip to content
Open
4 changes: 2 additions & 2 deletions .code-samples.meilisearch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |-
Expand Down
8 changes: 3 additions & 5 deletions meilisearch/_httprequests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
import requests

from meilisearch.config import Config
from meilisearch.errors import (
MeilisearchApiError,
MeilisearchCommunicationError,
MeilisearchTimeoutError,
)
from meilisearch.errors import (MeilisearchApiError,
MeilisearchCommunicationError,
MeilisearchTimeoutError)
from meilisearch.models.index import PrefixSearch, ProximityPrecision
from meilisearch.version import qualified_version

Expand Down
44 changes: 33 additions & 11 deletions meilisearch/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,18 @@
import json
import re
from collections.abc import Iterator, Mapping, MutableMapping, Sequence
from typing import (
Any,
)
from typing import Any, Dict, Optional, Union
from urllib import parse

from meilisearch._httprequests import HttpRequests
from meilisearch.config import Config
from meilisearch.errors import ( # noqa: F401
MeilisearchApiError,
MeilisearchCommunicationError,
MeilisearchError,
)
from meilisearch.errors import MeilisearchApiError # noqa: F401
from meilisearch.errors import MeilisearchCommunicationError, 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.task import (Batch, BatchResults, Task, TaskInfo,
TaskResults)
from meilisearch.models.webhook import Webhook, WebhooksResults
from meilisearch.task import TaskHandler

Expand Down Expand Up @@ -346,12 +343,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:
Expand All @@ -362,7 +373,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.
Expand Down
64 changes: 38 additions & 26 deletions meilisearch/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

from collections.abc import Generator, Mapping, MutableMapping, Sequence
from datetime import datetime
from typing import (
TYPE_CHECKING,
Any,
)
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from urllib import parse
from warnings import warn

Expand All @@ -15,26 +12,16 @@
from meilisearch._utils import iso_to_date_time
from meilisearch.config import Config
from meilisearch.errors import version_error_hint_message
from meilisearch.models.document import Document, DocumentsResults, FieldsResults
from meilisearch.models.embedders import (
CompositeEmbedder,
Embedders,
EmbedderType,
HuggingFaceEmbedder,
OllamaEmbedder,
OpenAiEmbedder,
RestEmbedder,
UserProvidedEmbedder,
)
from meilisearch.models.index import (
Faceting,
IndexStats,
LocalizedAttributes,
Pagination,
PrefixSearch,
ProximityPrecision,
TypoTolerance,
)
from meilisearch.models.document import (Document, DocumentsResults,
FieldsResults)
from meilisearch.models.embedders import (CompositeEmbedder, Embedders,
EmbedderType, HuggingFaceEmbedder,
OllamaEmbedder, OpenAiEmbedder,
RestEmbedder, UserProvidedEmbedder)
from meilisearch.models.index import (Faceting, IndexStats,
LocalizedAttributes, Pagination,
PrefixSearch, ProximityPrecision,
SizeFormat, TypoTolerance)
from meilisearch.models.task import Task, TaskInfo, TaskResults
from meilisearch.task import TaskHandler

Expand Down Expand Up @@ -304,12 +291,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:
Expand All @@ -320,7 +321,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
Expand Down
8 changes: 7 additions & 1 deletion meilisearch/models/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections.abc import Iterator
from enum import Enum
from typing import Any
from typing import Any, Dict, Optional

from camel_converter.pydantic_base import CamelBase
from pydantic import ConfigDict, field_validator
Expand All @@ -25,12 +25,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
Expand Down
3 changes: 2 additions & 1 deletion meilisearch/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from meilisearch._httprequests import HttpRequests
from meilisearch.config import Config
from meilisearch.errors import MeilisearchTimeoutError
from meilisearch.models.task import Batch, BatchResults, Task, TaskInfo, TaskResults
from meilisearch.models.task import (Batch, BatchResults, Task, TaskInfo,
TaskResults)


class TaskHandler:
Expand Down
3 changes: 2 additions & 1 deletion tests/client/test_chat_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import pytest
import requests

from meilisearch.errors import MeilisearchApiError, MeilisearchCommunicationError
from meilisearch.errors import (MeilisearchApiError,
MeilisearchCommunicationError)


class MockStreamingResponse:
Expand Down
60 changes: 60 additions & 0 deletions tests/client/test_client_stats_meilisearch.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -12,3 +18,57 @@ 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()
)
3 changes: 2 additions & 1 deletion tests/errors/test_timeout_error_meilisearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import requests

import meilisearch
from meilisearch.errors import MeilisearchCommunicationError, MeilisearchTimeoutError
from meilisearch.errors import (MeilisearchCommunicationError,
MeilisearchTimeoutError)
from tests import BASE_URL, MASTER_KEY


Expand Down
42 changes: 41 additions & 1 deletion tests/index/test_index_stats_meilisearch.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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())
Loading