Skip to content

Commit cb10809

Browse files
committed
Add support for brotli compression
1 parent c60644f commit cb10809

File tree

4 files changed

+149
-6
lines changed

4 files changed

+149
-6
lines changed

poetry.lock

Lines changed: 111 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ requires-python = ">=3.10"
88
dependencies = [
99
"pydantic>=2.12.0",
1010
"pyarrow>=22.0.0",
11+
"brotli (>=1.2.0,<2.0.0)",
1112
]
1213
license = "MIT"
1314
license-files = ['LICENSE']

src/cvec/cvec.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import os
55
import time
66
import zlib
7+
8+
import brotli
79
from datetime import datetime
810
from typing import Any, Dict, List, Optional
911
from urllib.error import HTTPError, URLError
@@ -113,7 +115,7 @@ def _get_headers(self) -> Dict[str, str]:
113115
"Authorization": f"Bearer {self._access_token}",
114116
"Content-Type": "application/json",
115117
"Accept": "application/json",
116-
"Accept-Encoding": "gzip, deflate",
118+
"Accept-Encoding": "br, gzip, deflate",
117119
}
118120

119121
@staticmethod
@@ -125,7 +127,9 @@ def _read_response(response: Any) -> tuple[bytes, str]:
125127
"""
126128
raw = response.read()
127129
encoding = response.headers.get("Content-Encoding", "")
128-
if encoding == "gzip":
130+
if encoding == "br":
131+
raw = brotli.decompress(raw)
132+
elif encoding == "gzip":
129133
raw = gzip.decompress(raw)
130134
elif encoding == "deflate":
131135
raw = zlib.decompress(raw)

tests/test_http_compression.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
"""Tests for HTTP compression support (gzip/deflate)."""
1+
"""Tests for HTTP compression support (gzip/deflate/brotli)."""
22

33
import gzip
44
import json
55
import zlib
66
from typing import Any
77
from unittest.mock import Mock, patch
88

9+
import brotli
10+
911
from cvec import CVec
1012

1113

@@ -63,7 +65,7 @@ def test_accept_encoding_header_sent(
6365
mock_fetch_key: Any,
6466
mock_login: Any,
6567
) -> None:
66-
"""Verify Accept-Encoding: gzip, deflate is present in request headers."""
68+
"""Verify Accept-Encoding includes br, gzip, deflate."""
6769
client = _create_client()
6870

6971
mock_response = _make_mock_response(b"[]")
@@ -72,7 +74,7 @@ def test_accept_encoding_header_sent(
7274
client.get_metrics()
7375

7476
req = mock_urlopen.call_args[0][0]
75-
assert req.get_header("Accept-encoding") == "gzip, deflate"
77+
assert req.get_header("Accept-encoding") == "br, gzip, deflate"
7678

7779
@patch.object(CVec, "_login_with_supabase", return_value=None)
7880
@patch.object(
@@ -196,3 +198,29 @@ def test_gzip_arrow_response_decompressed(
196198
result = client._make_request("GET", "/api/metrics/data/arrow")
197199
assert isinstance(result, bytes)
198200
assert result == arrow_bytes
201+
202+
@patch.object(CVec, "_login_with_supabase", return_value=None)
203+
@patch.object(
204+
CVec,
205+
"_fetch_config",
206+
autospec=True,
207+
side_effect=mock_fetch_config_side_effect,
208+
)
209+
@patch("cvec.cvec.urlopen")
210+
def test_brotli_response_decompressed(
211+
self,
212+
mock_urlopen: Any,
213+
mock_fetch_key: Any,
214+
mock_login: Any,
215+
) -> None:
216+
"""Mock a brotli-compressed JSON response, verify it is decompressed."""
217+
client = _create_client()
218+
219+
data = [{"id": 4, "name": "metric4"}]
220+
compressed = brotli.compress(json.dumps(data).encode("utf-8"))
221+
mock_response = _make_mock_response(compressed, content_encoding="br")
222+
mock_urlopen.return_value = mock_response
223+
224+
result = client.get_metrics()
225+
assert len(result) == 1
226+
assert result[0].name == "metric4"

0 commit comments

Comments
 (0)