Skip to content

Commit 7ec6ac3

Browse files
committed
fix(meilisearch): add Meilisearch community module
1 parent 5c70d54 commit 7ec6ac3

8 files changed

Lines changed: 257 additions & 63 deletions

File tree

docs/community/meilisearch.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.. autoclass:: testcontainers.community.meilisearch.MeilisearchContainer
2+
.. title:: testcontainers.community.meilisearch.MeilisearchContainer

docs/modules/meilisearch.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Meilisearch
2+
3+
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>
4+
5+
## Introduction
6+
7+
The Testcontainers module for Meilisearch.
8+
9+
## Adding this module to your project dependencies
10+
11+
Please run the following command to add the Meilisearch module to your python dependencies:
12+
13+
```bash
14+
pip install testcontainers[meilisearch] meilisearch
15+
```
16+
17+
## Usage example
18+
19+
<!--codeinclude-->
20+
21+
[Creating a Meilisearch container](meilisearch_example.py)
22+
23+
<!--/codeinclude-->
24+
25+
## Configuration
26+
27+
The Meilisearch container can be configured with the following parameters:
28+
29+
- `image`: Docker image to use (default: `getmeili/meilisearch:v1.53`)
30+
- `port`: Port to expose (default: `7700`)
31+
- `meili_env`: Value of `MEILI_ENV` (default: `production`)
32+
- `master_key`: Master key; a random one is generated if not provided. Must be at least 16 bytes when `meili_env` is `production`
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from meilisearch import Client
2+
3+
from testcontainers.community.meilisearch import MeilisearchContainer
4+
5+
6+
def basic_example():
7+
with MeilisearchContainer() as meili:
8+
client = Client(meili.get_connection_url(), meili.get_master_key())
9+
10+
print(f"Health: {client.health()['status']}")
11+
12+
index = client.index("movies")
13+
14+
documents = [
15+
{"id": 1, "title": "Interstellar", "genre": "sci-fi"},
16+
{"id": 2, "title": "Arrival", "genre": "sci-fi"},
17+
{"id": 3, "title": "The Grand Budapest Hotel", "genre": "comedy"},
18+
]
19+
20+
task = index.add_documents(documents)
21+
client.wait_for_task(task.task_uid)
22+
print(f"Indexed {len(documents)} documents")
23+
24+
results = index.search("interstelar")
25+
print("\nSearch results (typo-tolerant):")
26+
for hit in results["hits"]:
27+
print(f" {hit['id']}: {hit['title']}")
28+
29+
results = index.search("", {"filter": None, "limit": 2})
30+
print(f"\nTotal hits with empty query: {len(results['hits'])}")
31+
32+
33+
if __name__ == "__main__":
34+
basic_example()

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ nav:
6262
- modules/db2.md
6363
- modules/elasticsearch.md
6464
- modules/influxdb.md
65+
- modules/meilisearch.md
6566
- modules/mongodb.md
6667
- modules/mssql.md
6768
- modules/mysql.md

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ kafka = []
8282
keycloak = ["python-keycloak>=6; python_version < '4.0'"]
8383
localstack = ["boto3>=1"]
8484
mailpit = ["cryptography"]
85+
meilisearch = []
8586
memcached = []
8687
minio = ["minio>=7"]
8788
milvus = []
@@ -130,6 +131,7 @@ test = [
130131
"cassandra-driver>=3.30",
131132
"kafka-python-ng>=2",
132133
"hvac>=2; python_version < '4.0'",
134+
"meilisearch>=0.43.0",
133135
"pymilvus>=2",
134136
"paho-mqtt>=2",
135137
"sqlalchemy-cockroachdb>=2",
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import os
2+
import secrets
3+
from typing import Optional
4+
5+
from testcontainers.core.container import DockerContainer
6+
from testcontainers.core.wait_strategies import HttpWaitStrategy
7+
8+
9+
class MeilisearchContainer(DockerContainer):
10+
"""
11+
Meilisearch container.
12+
13+
Example:
14+
15+
.. doctest::
16+
17+
>>> from meilisearch import Client
18+
>>> from testcontainers.community.meilisearch import MeilisearchContainer
19+
20+
>>> with MeilisearchContainer() as meili:
21+
... client = Client(meili.get_connection_url(), meili.get_master_key())
22+
... client.health()["status"]
23+
'available'
24+
"""
25+
26+
def __init__(
27+
self,
28+
image: str = "getmeili/meilisearch:v1.53",
29+
port: int = 7700,
30+
meili_env: Optional[str] = None,
31+
master_key: Optional[str] = None,
32+
**kwargs,
33+
) -> None:
34+
super().__init__(image=image, **kwargs)
35+
36+
self.port = port
37+
self.meili_env = meili_env or os.environ.get("MEILI_ENV", "production")
38+
self.master_key = master_key or os.environ.get("MEILI_MASTER_KEY", secrets.token_urlsafe(32))
39+
40+
if self.meili_env == "production" and len(self.master_key.encode()) < 16:
41+
raise ValueError("master key must be at least 16 bytes when MEILI_ENV=production")
42+
43+
self.with_exposed_ports(self.port)
44+
self.with_env("MEILI_ENV", self.meili_env)
45+
self.with_env("MEILI_MASTER_KEY", self.master_key)
46+
self.with_env("MEILI_NO_ANALYTICS", "true")
47+
48+
self.waiting_for(HttpWaitStrategy(self.port, "/health"))
49+
50+
def get_master_key(self) -> str:
51+
return self.master_key
52+
53+
def get_connection_url(self) -> str:
54+
host = self.get_container_host_ip()
55+
port = self.get_exposed_port(self.port)
56+
return f"http://{host}:{port}"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
from meilisearch import Client
3+
from meilisearch.errors import MeilisearchApiError
4+
5+
from testcontainers.community.meilisearch import MeilisearchContainer
6+
7+
8+
def test_docker_run_meilisearch():
9+
with MeilisearchContainer() as meili:
10+
client = Client(meili.get_connection_url(), meili.get_master_key())
11+
12+
assert client.health()["status"] == "available"
13+
14+
index = client.index("movies")
15+
task = index.add_documents([{"id": 1, "title": "Movie"}])
16+
client.wait_for_task(task.task_uid)
17+
18+
assert index.search("Movie")["hits"][0]["id"] == 1
19+
20+
21+
def test_docker_run_meilisearch_requires_master_key():
22+
with MeilisearchContainer() as meili:
23+
client = Client(meili.get_connection_url())
24+
25+
with pytest.raises(MeilisearchApiError):
26+
client.get_keys()
27+
28+
29+
def test_meilisearch_rejects_short_master_key_in_production():
30+
with pytest.raises(ValueError):
31+
MeilisearchContainer(master_key="short")
32+
33+
34+
def test_meilisearch_allows_short_master_key_in_development():
35+
container = MeilisearchContainer(meili_env="development", master_key="short")
36+
assert container.get_master_key() == "short"

0 commit comments

Comments
 (0)