Skip to content

Commit d588e52

Browse files
authored
Use backports.zstd on Python 3.13 and earlier (#1146)
1 parent 2b13fa9 commit d588e52

7 files changed

Lines changed: 108 additions & 115 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ As well as these optional installs:
149149
* `rich` - Rich terminal support. *(Optional, with `httpx2[cli]`)*
150150
* `click` - Command line client support. *(Optional, with `httpx2[cli]`)*
151151
* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx2[brotli]`)*
152-
* `zstandard` - Decoding for "zstd" compressed responses on Python 3.13 and below. *(Optional, with `httpx2[zstd]`. On Python 3.14+, `zstd` is supported via the stdlib [`compression.zstd`](https://docs.python.org/3/library/compression.zstd.html) module when it is available; the `httpx2[zstd]` extra installs nothing there, so decoding falls back to `zstandard` only on 3.13 and below.)*
152+
* `backports.zstd` - Decoding for "zstd" compressed responses on Python 3.13 and below. *(Optional, with `httpx2[zstd]`. On Python 3.14+, `zstd` is supported via the stdlib [`compression.zstd`](https://docs.python.org/3/library/compression.zstd.html) module when it is available, and the `httpx2[zstd]` extra installs nothing.)*
153153

154154
A huge amount of credit is due to `requests` for the API layout that
155155
much of this work follows, as well as to `urllib3` for plenty of design

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ As well as these optional installs:
119119
* `rich` - Rich terminal support. *(Optional, with `httpx2[cli]`)*
120120
* `click` - Command line client support. *(Optional, with `httpx2[cli]`)*
121121
* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx2[brotli]`)*
122-
* `zstandard` - Decoding for "zstd" compressed responses on Python 3.13 and below. *(Optional, with `httpx2[zstd]`. On Python 3.14+, `zstd` is supported via the stdlib [`compression.zstd`][] module when it is available; the `httpx2[zstd]` extra installs nothing there, so decoding falls back to `zstandard` only on 3.13 and below.)*
122+
* `backports.zstd` - Decoding for "zstd" compressed responses on Python 3.13 and below. *(Optional, with `httpx2[zstd]`. On Python 3.14+, `zstd` is supported via the stdlib [`compression.zstd`][] module when it is available, and the `httpx2[zstd]` extra installs nothing.)*
123123

124124
A huge amount of credit is due to `requests` for the API layout that
125125
much of this work follows, as well as to `urllib3` for plenty of design

docs/quickstart.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Any `gzip` and `deflate` HTTP response encodings will automatically
102102
be decoded for you. If `brotlipy` is installed, then the `brotli` response
103103
encoding will be supported. The `zstd` response encoding is supported
104104
on Python 3.14+ via the stdlib [`compression.zstd`][] module when it is
105-
available; on Python 3.13 and below it requires the `zstandard` package.
105+
available; on Python 3.13 and below it requires the `backports.zstd` package.
106106

107107
For example, to create an image from binary data returned by a request, you can use the following code:
108108

src/httpx2/httpx2/_decoders.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from __future__ import annotations
88

99
import codecs
10-
import functools
1110
import io
1211
import sys
1312
import typing
@@ -31,30 +30,22 @@
3130
# Zstandard support is optional on Python <= 3.13.
3231
# On Python 3.14+, the stdlib includes an optional built-in zstd implementation.
3332
if typing.TYPE_CHECKING:
34-
# We keep checking Python version in the type checker path because try..except doesn't help type checkers.
3533
if sys.version_info >= (3, 14):
3634
from compression.zstd import ZstdDecompressor, ZstdError
3735
else:
38-
from zstandard import ZstdDecompressor as _ZstdDecompressor, ZstdError
36+
from backports.zstd import ZstdDecompressor, ZstdError
3937

40-
ZstdDecompressor = functools.partial(_ZstdDecompressor().decompressobj)
41-
42-
_zstandard_installed: bool = False
38+
_zstandard_installed: bool
4339
else: # pragma: no cover
44-
_zstandard_installed = False
4540
try:
46-
from compression.zstd import ZstdDecompressor, ZstdError
41+
if sys.version_info >= (3, 14):
42+
from compression.zstd import ZstdDecompressor, ZstdError
43+
else:
44+
from backports.zstd import ZstdDecompressor, ZstdError
4745

4846
_zstandard_installed = True
49-
# Either Python <3.14 or the distro doesn't have `compression.zstd`.
5047
except ImportError:
51-
try:
52-
from zstandard import ZstdDecompressor as _ZstdDecompressor, ZstdError
53-
54-
ZstdDecompressor = functools.partial(_ZstdDecompressor().decompressobj)
55-
_zstandard_installed = True
56-
except ImportError:
57-
pass
48+
_zstandard_installed = False
5849

5950

6051
class ContentDecoder:
@@ -185,8 +176,7 @@ def flush(self) -> bytes:
185176
class ZStandardDecoder(ContentDecoder):
186177
"""Handle 'zstd' RFC 8878 decoding.
187178
188-
If running on Python 3.14+ or a distro that doesn't have the `compression.zstd` stdlib module, requires either:
189-
`pip install zstandard` or `pip install httpx2[zstd]`.
179+
On Python 3.13 and below, this requires `pip install httpx2[zstd]`.
190180
"""
191181

192182
# inspired by the ZstdDecoder implementation in urllib3

src/httpx2/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ ws = [
7474
]
7575
# TODO(Marcelo): Remove when Python 3.13 reaches EOL.
7676
zstd = [
77-
"zstandard>=0.18.0; python_version <= '3.13'",
77+
"backports.zstd>=1.0.0; python_version <= '3.13'",
7878
]
7979

8080
[project.scripts]

tests/httpx2/test_decoders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
if sys.version_info >= (3, 14): # pragma: no cover
1414
from compression import zstd
1515
else: # pragma: no cover
16-
import zstandard as zstd
16+
from backports import zstd
1717

1818

1919
def test_deflate() -> None:

0 commit comments

Comments
 (0)