Skip to content

Commit 229639a

Browse files
author
07pepa
committed
Add http/2 capabilities
* added http/2 protocol capability for sync/async clients * modified github workflow to generate cert * modified test code to work with generated certificate
1 parent a05ceba commit 229639a

File tree

17 files changed

+211
-66
lines changed

17 files changed

+211
-66
lines changed

.github/workflows/testing.yaml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
pull_request:
88
env:
99
UV_CACHE_DIR: /tmp/.uv-cache
10-
PYTHON_VERSION: "3.9"
10+
PYTHON_VERSION: "3.10"
1111
jobs:
1212
linting:
1313
runs-on: ubuntu-latest
@@ -63,7 +63,15 @@ jobs:
6363
- name: Install Dependencies
6464
run: just install
6565
- name: Test with pytest
66-
run: just test-parallel-ci
66+
run: |
67+
sudo apt-get update && \
68+
sudo apt-get install -y libnss3-tools build-essential gcc && \
69+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" && \
70+
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && \
71+
brew install mkcert && \
72+
mkcert -install && \
73+
mkcert -key-file meilisearch.key -cert-file meilisearch.crt localhost 127.0.0.1 ::1 && \
74+
just test-parallel-ci
6775
- name: Upload coverage
6876
uses: codecov/codecov-action@v4
6977
with:
@@ -99,7 +107,15 @@ jobs:
99107
- name: Install Dependencies
100108
run: just install
101109
- name: Test with pytest
102-
run: just test-no-parallel-ci
110+
run: |
111+
sudo apt-get update && \
112+
sudo apt-get install -y libnss3-tools build-essential gcc && \
113+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" && \
114+
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && \
115+
brew install mkcert && \
116+
mkcert -install && \
117+
mkcert -key-file meilisearch.key -cert-file meilisearch.crt localhost 127.0.0.1 ::1 && \
118+
just test-no-parallel-ci
103119
- name: Upload coverage
104120
uses: codecov/codecov-action@v4
105121
with:

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
meilisearch.crt
2+
meilisearch.key
13
# Byte-compiled / optimized / DLL files
24
__pycache__/
35
*.py[cod]

benchmark/run_benchmark.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def benchmark_meili_add_documents_in_batches(
103103
async def run_async_batch_add_benchmark(data: Sequence[JsonMapping]) -> list[float]:
104104
times = []
105105
for _ in track(range(10), description="Running async add in batches benchmark..."):
106-
async with AsyncClient("http://127.0.0.1:7700", "masterKey") as client:
106+
async with AsyncClient("https://127.0.0.1:7700", "masterKey", verify=False) as client:
107107
index = client.index("movies")
108108
_, time_taken = await benchmark_async_add_document_in_batches(client, data)
109109
times.append(time_taken)
@@ -118,7 +118,7 @@ async def run_async_batch_add_benchmark(data: Sequence[JsonMapping]) -> list[flo
118118
async def run_async_search_benchmark(movies_sampled: list[str]) -> list[float]:
119119
times = []
120120
for _ in track(range(10), description="Running async multi search benchmark..."):
121-
async with AsyncClient("http://127.0.0.1:7700", "masterKey") as client:
121+
async with AsyncClient("https://127.0.0.1:7700", "masterKey") as client:
122122
index = client.index("movies")
123123
searches = []
124124
for movie in movies_sampled:
@@ -145,7 +145,7 @@ async def setup_index(data: Sequence[JsonMapping]) -> None:
145145
def run_sync_batch_add_benchmark(data: Sequence[JsonMapping]) -> list[float]:
146146
times = []
147147
for _ in track(range(10), description="Running sync add in batches benchmark..."):
148-
client = Client("http://127.0.0.1:7700", "masterKey")
148+
client = Client("https://127.0.0.1:7700", "masterKey", verify=False)
149149
index = client.index("movies")
150150
_, time_taken = benchmark_sync_add_document_in_batches(client, data)
151151
times.append(time_taken)
@@ -158,7 +158,7 @@ def run_sync_batch_add_benchmark(data: Sequence[JsonMapping]) -> list[float]:
158158

159159

160160
def run_sync_search_benchmark(movies_sampled: list[str]) -> list[float]:
161-
client = Client("http://127.0.0.1:7700", "masterKey")
161+
client = Client("https://127.0.0.1:7700", "masterKey")
162162
index = client.index("movies")
163163
times = []
164164
for _ in track(range(10), description="Running sync multi search benchmark..."):
@@ -174,7 +174,7 @@ def run_sync_search_benchmark(movies_sampled: list[str]) -> list[float]:
174174
def run_meili_batch_add_benchmark(data: Sequence[JsonMapping]) -> list[float]:
175175
times = []
176176
for _ in track(range(10), description="Running meili add in batches benchmark..."):
177-
client = MeilisearchClient("http://127.0.0.1:7700", "masterKey")
177+
client = MeilisearchClient("https://127.0.0.1:7700", "masterKey")
178178
index = client.index("movies")
179179
_, time_taken = benchmark_meili_add_documents_in_batches(client, data)
180180
times.append(time_taken)
@@ -187,7 +187,7 @@ def run_meili_batch_add_benchmark(data: Sequence[JsonMapping]) -> list[float]:
187187

188188

189189
def run_meili_search_benchmark(movies_sampled: list[str]) -> list[float]:
190-
client = MeilisearchClient("http://127.0.0.1:7700", "masterKey")
190+
client = MeilisearchClient("https://127.0.0.1:7700", "masterKey")
191191
index = client.index("movies")
192192
times = []
193193
for _ in track(range(10), description="Running meili multi search benchmark..."):

docker-compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ services:
66
environment:
77
- MEILI_MASTER_KEY=masterKey
88
- MEILI_NO_ANALYTICS=true
9+
volumes:
10+
- ./meilisearch.key:/meilisearch.key
11+
- ./meilisearch.crt:/meilisearch.crt
12+
command: ["meilisearch", "--ssl-cert-path", "/meilisearch.crt", "--ssl-key-path", "/meilisearch.key"]

examples/add_documents_decorator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
@add_documents(
1111
index_name="movies",
12-
connection_info=ConnectionInfo(url="http://127.0.0.1:7700", api_key="masterKey"),
12+
connection_info=ConnectionInfo(url="https://127.0.0.1:7700", api_key="masterKey"),
1313
)
1414
def load_documents() -> list[dict[str, Any]]:
1515
with open("../datasets/small_movies.json") as f:

justfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050

5151
@install:
5252
uv sync --frozen --all-extras
53+
uv pip install truststore || true # truststore is not available for Python 3.9
5354

5455
@benchmark: start-meilisearch-detached && stop-meilisearch
5556
-uv run benchmark/run_benchmark.py

meilisearch_python_sdk/_client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ def __init__(
150150
verify: str | bool | SSLContext = True,
151151
custom_headers: dict[str, str] | None = None,
152152
json_handler: BuiltinHandler | OrjsonHandler | UjsonHandler | None = None,
153+
http2: bool = False,
153154
) -> None:
154155
"""Class initializer.
155156
@@ -168,11 +169,12 @@ def __init__(
168169
(uses the json module from the standard library), OrjsonHandler (uses orjson), or
169170
UjsonHandler (uses ujson). Note that in order use orjson or ujson the corresponding
170171
extra needs to be included. Default: BuiltinHandler.
172+
http2: Whether or not to use HTTP/2. Defaults to False.
171173
"""
172174
super().__init__(api_key, custom_headers, json_handler)
173175

174176
self.http_client = HttpxAsyncClient(
175-
base_url=url, timeout=timeout, headers=self._headers, verify=verify
177+
base_url=url, timeout=timeout, headers=self._headers, verify=verify, http2=http2
176178
)
177179
self._http_requests = AsyncHttpRequests(self.http_client, json_handler=self.json_handler)
178180

@@ -1073,6 +1075,7 @@ def __init__(
10731075
verify: str | bool | SSLContext = True,
10741076
custom_headers: dict[str, str] | None = None,
10751077
json_handler: BuiltinHandler | OrjsonHandler | UjsonHandler | None = None,
1078+
http2: bool = False,
10761079
) -> None:
10771080
"""Class initializer.
10781081
@@ -1091,12 +1094,14 @@ def __init__(
10911094
(uses the json module from the standard library), OrjsonHandler (uses orjson), or
10921095
UjsonHandler (uses ujson). Note that in order use orjson or ujson the corresponding
10931096
extra needs to be included. Default: BuiltinHandler.
1097+
http2: If set to True, the client will use HTTP/2. Defaults to False.
10941098
"""
10951099
super().__init__(api_key, custom_headers, json_handler)
10961100

10971101
self.http_client = HttpxClient(
1098-
base_url=url, timeout=timeout, headers=self._headers, verify=verify
1102+
base_url=url, timeout=timeout, headers=self._headers, verify=verify, http2=http2
10991103
)
1104+
11001105
self._http_requests = HttpRequests(self.http_client, json_handler=self.json_handler)
11011106

11021107
def create_dump(self) -> TaskInfo:

meilisearch_python_sdk/decorators.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def async_add_documents(
2626
batch_size: int | None = None,
2727
primary_key: str | None = None,
2828
wait_for_task: bool = False,
29+
verify: bool = True,
2930
) -> Callable:
3031
"""Decorator that takes the returned documents from a function and asyncronously adds them to Meilisearch.
3132
@@ -42,6 +43,7 @@ def async_add_documents(
4243
Defaults to None.
4344
wait_for_task: If set to `True` the decorator will wait for the document addition to finish
4445
indexing before returning, otherwise it will return right away. Default = False.
46+
verify: If set to `False` the decorator will not verify the SSL certificate of the server.
4547
4648
Returns:
4749
@@ -89,7 +91,9 @@ async def wrapper(*args: Any, **kwargs: Any) -> Any:
8991
)
9092
return result
9193

92-
async with AsyncClient(connection_info.url, connection_info.api_key) as client:
94+
async with AsyncClient(
95+
connection_info.url, connection_info.api_key, verify=verify
96+
) as client:
9397
await _async_add_documents(
9498
client, index_name, result, batch_size, primary_key, wait_for_task
9599
)
@@ -108,6 +112,7 @@ def add_documents(
108112
batch_size: int | None = None,
109113
primary_key: str | None = None,
110114
wait_for_task: bool = False,
115+
verify: bool = True,
111116
) -> Callable:
112117
"""Decorator that takes the returned documents from a function and adds them to Meilisearch.
113118
@@ -124,6 +129,7 @@ def add_documents(
124129
Defaults to None.
125130
wait_for_task: If set to `True` the decorator will wait for the document addition to finish
126131
indexing before returning, otherwise it will return right away. Default = False.
132+
verify: If set to `False` the decorator will not verify the SSL certificate of the server.
127133
128134
Returns:
129135
@@ -171,7 +177,9 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
171177
)
172178
return result
173179

174-
decorator_client = Client(url=connection_info.url, api_key=connection_info.api_key)
180+
decorator_client = Client(
181+
url=connection_info.url, api_key=connection_info.api_key, verify=verify
182+
)
175183
_add_documents(
176184
decorator_client,
177185
index_name,

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ dependencies = [
2727
"camel-converter>=1.0.0",
2828
# allows pydantic to use pipe instead of Union
2929
"eval-type-backport>=0.2.0; python_version < '3.10'",
30-
"httpx>=0.17",
30+
"httpx[http2]>=0.17",
3131
"pydantic>=2.0.0",
3232
"PyJWT>=2.3.0",
3333
]

tests/conftest.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
import asyncio
22
import csv
33
import json
4+
import ssl
5+
import sys
6+
from logging import warning
47
from pathlib import Path
58
from uuid import uuid4
69

710
import pytest
11+
12+
try:
13+
import truststore
14+
15+
has_truststore = True
16+
except ImportError:
17+
if sys.version_info > (3, 10):
18+
warning(
19+
"truststore is not installed, SSL verification will not work. run pip install truststore"
20+
)
21+
has_truststore = True
822
from httpx import AsyncClient as HttpxAsyncClient
923

1024
from meilisearch_python_sdk import AsyncClient, Client
@@ -22,49 +36,62 @@
2236
)
2337

2438
MASTER_KEY = "masterKey"
25-
BASE_URL = "http://127.0.0.1:7700"
39+
BASE_URL = "https://127.0.0.1:7700"
2640

2741
ROOT_PATH = Path().absolute()
2842
SMALL_MOVIES_PATH = ROOT_PATH / "datasets" / "small_movies.json"
2943

44+
_SSL_VERIFY: ssl.SSLContext | bool = (
45+
truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) if has_truststore else False
46+
)
47+
48+
49+
@pytest.fixture(scope="session")
50+
async def ssl_verify():
51+
yield _SSL_VERIFY
52+
3053

3154
@pytest.fixture
3255
async def async_client():
33-
async with AsyncClient(BASE_URL, MASTER_KEY) as client:
56+
async with AsyncClient(BASE_URL, MASTER_KEY, verify=_SSL_VERIFY) as client:
3457
yield client
3558

3659

3760
@pytest.fixture
3861
async def async_client_orjson_handler():
39-
async with AsyncClient(BASE_URL, MASTER_KEY, json_handler=OrjsonHandler()) as client:
62+
async with AsyncClient(
63+
BASE_URL, MASTER_KEY, json_handler=OrjsonHandler(), verify=_SSL_VERIFY
64+
) as client:
4065
yield client
4166

4267

4368
@pytest.fixture
4469
async def async_client_ujson_handler():
45-
async with AsyncClient(BASE_URL, MASTER_KEY, json_handler=UjsonHandler()) as client:
70+
async with AsyncClient(
71+
BASE_URL, MASTER_KEY, json_handler=UjsonHandler(), verify=_SSL_VERIFY
72+
) as client:
4673
yield client
4774

4875

4976
@pytest.fixture
5077
async def async_client_with_plugins():
51-
async with AsyncClient(BASE_URL, MASTER_KEY) as client:
78+
async with AsyncClient(BASE_URL, MASTER_KEY, verify=_SSL_VERIFY) as client:
5279
yield client
5380

5481

5582
@pytest.fixture
5683
def client():
57-
yield Client(BASE_URL, MASTER_KEY)
84+
yield Client(BASE_URL, MASTER_KEY, verify=_SSL_VERIFY)
5885

5986

6087
@pytest.fixture
6188
def client_orjson_handler():
62-
yield Client(BASE_URL, MASTER_KEY, json_handler=OrjsonHandler())
89+
yield Client(BASE_URL, MASTER_KEY, json_handler=OrjsonHandler(), verify=_SSL_VERIFY)
6390

6491

6592
@pytest.fixture
6693
def client_ujson_handler():
67-
yield Client(BASE_URL, MASTER_KEY, json_handler=UjsonHandler())
94+
yield Client(BASE_URL, MASTER_KEY, json_handler=UjsonHandler(), verify=_SSL_VERIFY)
6895

6996

7097
@pytest.fixture(autouse=True)
@@ -254,7 +281,7 @@ async def default_search_key(async_client):
254281
@pytest.fixture(scope="session", autouse=True)
255282
async def enable_vector_search():
256283
async with HttpxAsyncClient(
257-
base_url=BASE_URL, headers={"Authorization": f"Bearer {MASTER_KEY}"}
284+
base_url=BASE_URL, headers={"Authorization": f"Bearer {MASTER_KEY}"}, verify=_SSL_VERIFY
258285
) as client:
259286
await client.patch("/experimental-features", json={"vectorStore": True})
260287
yield
@@ -263,7 +290,7 @@ async def enable_vector_search():
263290
@pytest.fixture(scope="session", autouse=True)
264291
async def enable_edit_by_function():
265292
async with HttpxAsyncClient(
266-
base_url=BASE_URL, headers={"Authorization": f"Bearer {MASTER_KEY}"}
293+
base_url=BASE_URL, headers={"Authorization": f"Bearer {MASTER_KEY}"}, verify=_SSL_VERIFY
267294
) as client:
268295
await client.patch("/experimental-features", json={"editDocumentsByFunction": True})
269296
yield

0 commit comments

Comments
 (0)