Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/community/meilisearch.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.. autoclass:: testcontainers.community.meilisearch.MeilisearchContainer
.. title:: testcontainers.community.meilisearch.MeilisearchContainer
32 changes: 32 additions & 0 deletions docs/modules/meilisearch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Meilisearch

Since testcontainers-python <a href="https://github.com/testcontainers/testcontainers-python/releases/tag/v4.15.0"><span class="tc-version">:material-tag: v4.15.0</span></a>

## Introduction

The Testcontainers module for Meilisearch.

## Adding this module to your project dependencies

Please run the following command to add the Meilisearch module to your python dependencies:

```bash
pip install testcontainers[meilisearch] meilisearch
```

## Usage example

<!--codeinclude-->

[Creating a Meilisearch container](meilisearch_example.py)

<!--/codeinclude-->

## Configuration

The Meilisearch container can be configured with the following parameters:

- `image`: Docker image to use (default: `getmeili/meilisearch:v1.53`)
- `port`: Port to expose (default: `7700`)
- `meili_env`: Value of `MEILI_ENV` (default: `production`)
- `master_key`: Master key; a random one is generated if not provided. Must be at least 16 bytes when `meili_env` is `production`
34 changes: 34 additions & 0 deletions docs/modules/meilisearch_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from meilisearch import Client

from testcontainers.community.meilisearch import MeilisearchContainer


def basic_example():
with MeilisearchContainer() as meili:
client = Client(meili.get_connection_url(), meili.get_master_key())

print(f"Health: {client.health()['status']}")

index = client.index("movies")

documents = [
{"id": 1, "title": "Interstellar", "genre": "sci-fi"},
{"id": 2, "title": "Arrival", "genre": "sci-fi"},
{"id": 3, "title": "The Grand Budapest Hotel", "genre": "comedy"},
]

task = index.add_documents(documents)
client.wait_for_task(task.task_uid)
print(f"Indexed {len(documents)} documents")

results = index.search("interstelar")
print("\nSearch results (typo-tolerant):")
for hit in results["hits"]:
print(f" {hit['id']}: {hit['title']}")

results = index.search("", {"filter": None, "limit": 2})
print(f"\nTotal hits with empty query: {len(results['hits'])}")


if __name__ == "__main__":
basic_example()
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ nav:
- modules/db2.md
- modules/elasticsearch.md
- modules/influxdb.md
- modules/meilisearch.md
- modules/mongodb.md
- modules/mssql.md
- modules/mysql.md
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ kafka = []
keycloak = ["python-keycloak>=6; python_version < '4.0'"]
localstack = ["boto3>=1"]
mailpit = ["cryptography"]
meilisearch = []
memcached = []
minio = ["minio>=7"]
milvus = []
Expand Down Expand Up @@ -130,6 +131,7 @@ test = [
"cassandra-driver>=3.30",
"kafka-python-ng>=2",
"hvac>=2; python_version < '4.0'",
"meilisearch>=0.43.0",
"pymilvus>=2",
"paho-mqtt>=2",
"sqlalchemy-cockroachdb>=2",
Expand Down
56 changes: 56 additions & 0 deletions src/testcontainers/community/meilisearch/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import secrets
from typing import Optional

from testcontainers.core.container import DockerContainer
from testcontainers.core.wait_strategies import HttpWaitStrategy


class MeilisearchContainer(DockerContainer):
"""
Meilisearch container.

Example:

.. doctest::

>>> from meilisearch import Client
>>> from testcontainers.community.meilisearch import MeilisearchContainer

>>> with MeilisearchContainer() as meili:
... client = Client(meili.get_connection_url(), meili.get_master_key())
... client.health()["status"]
'available'
"""

def __init__(
self,
image: str = "getmeili/meilisearch:v1.53",
port: int = 7700,
meili_env: Optional[str] = None,
master_key: Optional[str] = None,
**kwargs,
) -> None:
super().__init__(image=image, **kwargs)

self.port = port
self.meili_env = meili_env or os.environ.get("MEILI_ENV", "production")
self.master_key = master_key or os.environ.get("MEILI_MASTER_KEY", secrets.token_urlsafe(32))

if self.meili_env == "production" and len(self.master_key.encode()) < 16:
raise ValueError("master key must be at least 16 bytes when MEILI_ENV=production")

self.with_exposed_ports(self.port)
self.with_env("MEILI_ENV", self.meili_env)
self.with_env("MEILI_MASTER_KEY", self.master_key)
self.with_env("MEILI_NO_ANALYTICS", "true")

self.waiting_for(HttpWaitStrategy(self.port, "/health"))

def get_master_key(self) -> str:
return self.master_key

def get_connection_url(self) -> str:
host = self.get_container_host_ip()
port = self.get_exposed_port(self.port)
return f"http://{host}:{port}"
36 changes: 36 additions & 0 deletions tests/community/meilisearch/test_meilisearch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pytest
from meilisearch import Client
from meilisearch.errors import MeilisearchApiError

from testcontainers.community.meilisearch import MeilisearchContainer


def test_docker_run_meilisearch():
with MeilisearchContainer() as meili:
client = Client(meili.get_connection_url(), meili.get_master_key())

assert client.health()["status"] == "available"

index = client.index("movies")
task = index.add_documents([{"id": 1, "title": "Movie"}])
client.wait_for_task(task.task_uid)

assert index.search("Movie")["hits"][0]["id"] == 1


def test_docker_run_meilisearch_requires_master_key():
with MeilisearchContainer() as meili:
client = Client(meili.get_connection_url())

with pytest.raises(MeilisearchApiError):
client.get_keys()


def test_meilisearch_rejects_short_master_key_in_production():
with pytest.raises(ValueError):
MeilisearchContainer(master_key="short")


def test_meilisearch_allows_short_master_key_in_development():
container = MeilisearchContainer(meili_env="development", master_key="short")
assert container.get_master_key() == "short"
Loading