Skip to content

Commit 4872fce

Browse files
Add support for ZSTD compression (#11161)
1 parent e6b8932 commit 4872fce

16 files changed

+129
-27
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ repos:
5555
rev: v1.5.0
5656
hooks:
5757
- id: yesqa
58+
additional_dependencies:
59+
- flake8-docstrings==1.6.0
60+
- flake8-no-implicit-concat==0.3.4
61+
- flake8-requirements==1.7.8
5862
- repo: https://github.com/PyCQA/isort
5963
rev: '6.0.1'
6064
hooks:

CHANGES/11161.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add support for Zstandard (aka Zstd) compression
2+
-- by :user:`KGuillaume-chaps`.

CONTRIBUTORS.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ Justin Foo
218218
Justin Turner Arthur
219219
Kay Zheng
220220
Kevin Samuel
221+
Kilian Guillaume
221222
Kimmo Parviainen-Jalanko
222223
Kirill Klenov
223224
Kirill Malovitsa

aiohttp/_http_parser.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ cdef class HttpParser:
437437
if enc is not None:
438438
self._content_encoding = None
439439
enc = enc.lower()
440-
if enc in ('gzip', 'deflate', 'br'):
440+
if enc in ('gzip', 'deflate', 'br', 'zstd'):
441441
encoding = enc
442442

443443
if self._cparser.type == cparser.HTTP_REQUEST:

aiohttp/client_reqrep.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
InvalidURL,
4545
ServerFingerprintMismatch,
4646
)
47-
from .compression_utils import HAS_BROTLI
47+
from .compression_utils import HAS_BROTLI, HAS_ZSTD
4848
from .formdata import FormData
4949
from .hdrs import CONTENT_TYPE
5050
from .helpers import (
@@ -105,7 +105,15 @@
105105

106106

107107
def _gen_default_accept_encoding() -> str:
108-
return "gzip, deflate, br" if HAS_BROTLI else "gzip, deflate"
108+
encodings = [
109+
"gzip",
110+
"deflate",
111+
]
112+
if HAS_BROTLI:
113+
encodings.append("br")
114+
if HAS_ZSTD:
115+
encodings.append("zstd")
116+
return ", ".join(encodings)
109117

110118

111119
@frozen_dataclass_decorator

aiohttp/compression_utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@
2121
except ImportError:
2222
HAS_BROTLI = False
2323

24+
if sys.version_info >= (3, 14):
25+
import compression.zstd # noqa: I900
26+
27+
HAS_ZSTD = True
28+
else:
29+
try:
30+
import zstandard
31+
32+
HAS_ZSTD = True
33+
except ImportError:
34+
HAS_ZSTD = False
35+
2436
MAX_SYNC_CHUNK_SIZE = 1024
2537

2638

@@ -276,3 +288,22 @@ def flush(self) -> bytes:
276288
if hasattr(self._obj, "flush"):
277289
return cast(bytes, self._obj.flush())
278290
return b""
291+
292+
293+
class ZSTDDecompressor:
294+
def __init__(self) -> None:
295+
if not HAS_ZSTD:
296+
raise RuntimeError(
297+
"The zstd decompression is not available. "
298+
"Please install `zstandard` module"
299+
)
300+
if sys.version_info >= (3, 14):
301+
self._obj = compression.zstd.ZstdDecompressor()
302+
else:
303+
self._obj = zstandard.ZstdDecompressor()
304+
305+
def decompress_sync(self, data: bytes) -> bytes:
306+
return self._obj.decompress(data)
307+
308+
def flush(self) -> bytes:
309+
return b""

aiohttp/http_parser.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@
2626

2727
from . import hdrs
2828
from .base_protocol import BaseProtocol
29-
from .compression_utils import HAS_BROTLI, BrotliDecompressor, ZLibDecompressor
29+
from .compression_utils import (
30+
HAS_BROTLI,
31+
HAS_ZSTD,
32+
BrotliDecompressor,
33+
ZLibDecompressor,
34+
ZSTDDecompressor,
35+
)
3036
from .helpers import (
3137
_EXC_SENTINEL,
3238
DEBUG,
@@ -527,7 +533,7 @@ def parse_headers(
527533
enc = headers.get(hdrs.CONTENT_ENCODING)
528534
if enc:
529535
enc = enc.lower()
530-
if enc in ("gzip", "deflate", "br"):
536+
if enc in ("gzip", "deflate", "br", "zstd"):
531537
encoding = enc
532538

533539
# chunking
@@ -930,14 +936,21 @@ def __init__(self, out: StreamReader, encoding: Optional[str]) -> None:
930936
self.encoding = encoding
931937
self._started_decoding = False
932938

933-
self.decompressor: Union[BrotliDecompressor, ZLibDecompressor]
939+
self.decompressor: Union[BrotliDecompressor, ZLibDecompressor, ZSTDDecompressor]
934940
if encoding == "br":
935941
if not HAS_BROTLI:
936942
raise ContentEncodingError(
937943
"Can not decode content-encoding: brotli (br). "
938944
"Please install `Brotli`"
939945
)
940946
self.decompressor = BrotliDecompressor()
947+
elif encoding == "zstd":
948+
if not HAS_ZSTD:
949+
raise ContentEncodingError(
950+
"Can not decode content-encoding: zstandard (zstd). "
951+
"Please install `zstandard`"
952+
)
953+
self.decompressor = ZSTDDecompressor()
941954
else:
942955
self.decompressor = ZLibDecompressor(encoding=encoding)
943956

docs/client_quickstart.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ You can enable ``brotli`` transfer-encodings support,
190190
just install `Brotli <https://pypi.org/project/Brotli/>`_
191191
or `brotlicffi <https://pypi.org/project/brotlicffi/>`_.
192192

193+
You can enable ``zstd`` transfer-encodings support,
194+
install `zstandard <https://pypi.org/project/zstandard/>`_.
195+
If you are using Python >= 3.14, no dependency should be required.
196+
193197
JSON Request
194198
============
195199

docs/spelling_wordlist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,3 +382,5 @@ www
382382
xxx
383383
yarl
384384
zlib
385+
zstandard
386+
zstd

requirements/lint.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ trustme
1414
uvloop; platform_system != "Windows"
1515
valkey
1616
zlib_ng
17+
zstandard; implementation_name == "cpython"

0 commit comments

Comments
 (0)