Skip to content

Commit 405a8c3

Browse files
committed
Use backports.zstd instead of zstandard
1 parent 636619e commit 405a8c3

6 files changed

Lines changed: 23 additions & 17 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ As well as these optional installs:
136136
* `rich` - Rich terminal support. *(Optional, with `httpx[cli]`)*
137137
* `click` - Command line client support. *(Optional, with `httpx[cli]`)*
138138
* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx[brotli]`)*
139-
* `zstandard` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)*
139+
* `backports.zstd` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)*
140140

141141
A huge amount of credit is due to `requests` for the API layout that
142142
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 `httpx[cli]`)*
120120
* `click` - Command line client support. *(Optional, with `httpx[cli]`)*
121121
* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx[brotli]`)*
122-
* `zstandard` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)*
122+
* `backports.zstd` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)*
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
@@ -100,7 +100,7 @@ b'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
100100

101101
Any `gzip` and `deflate` HTTP response encodings will automatically
102102
be decoded for you. If `brotlipy` is installed, then the `brotli` response
103-
encoding will be supported. If `zstandard` is installed, then `zstd`
103+
encoding will be supported. If `backports.zstd` is installed on Python < 3.14, then `zstd`
104104
response encodings will also be supported.
105105

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

httpx/_decoders.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@
2525
except ImportError:
2626
brotli = None
2727

28-
29-
# Zstandard support is optional
28+
# Zstandard support is optional on Python < 3.14
3029
try:
31-
import zstandard
32-
except ImportError: # pragma: no cover
33-
zstandard = None # type: ignore
30+
from compression import zstd
31+
except ImportError:
32+
try:
33+
from backports import zstd
34+
except ImportError:
35+
zstd = None # type: ignore[assignment]
3436

3537

3638
class ContentDecoder:
@@ -162,32 +164,32 @@ class ZStandardDecoder(ContentDecoder):
162164
"""
163165
Handle 'zstd' RFC 8878 decoding.
164166
165-
Requires `pip install zstandard`.
167+
Requires `pip install backports.zstd` on Python < 3.14.
166168
Can be installed as a dependency of httpx using `pip install httpx[zstd]`.
167169
"""
168170

169171
# inspired by the ZstdDecoder implementation in urllib3
170172
def __init__(self) -> None:
171-
if zstandard is None: # pragma: no cover
173+
if zstd is None: # pragma: no cover
172174
raise ImportError(
173175
"Using 'ZStandardDecoder', ..."
174176
"Make sure to install httpx using `pip install httpx[zstd]`."
175177
) from None
176178

177-
self.decompressor = zstandard.ZstdDecompressor().decompressobj()
179+
self.decompressor = zstd.ZstdDecompressor().decompressobj()
178180
self.seen_data = False
179181

180182
def decode(self, data: bytes) -> bytes:
181-
assert zstandard is not None
183+
assert zstd is not None
182184
self.seen_data = True
183185
output = io.BytesIO()
184186
try:
185187
output.write(self.decompressor.decompress(data))
186188
while self.decompressor.eof and self.decompressor.unused_data:
187189
unused_data = self.decompressor.unused_data
188-
self.decompressor = zstandard.ZstdDecompressor().decompressobj()
190+
self.decompressor = zstd.ZstdDecompressor().decompressobj()
189191
output.write(self.decompressor.decompress(unused_data))
190-
except zstandard.ZstdError as exc:
192+
except zstd.ZstdError as exc:
191193
raise DecodingError(str(exc)) from exc
192194
return output.getvalue()
193195

@@ -389,5 +391,5 @@ def flush(self) -> list[str]:
389391

390392
if brotli is None:
391393
SUPPORTED_DECODERS.pop("br") # pragma: no cover
392-
if zstandard is None:
394+
if zstd is None:
393395
SUPPORTED_DECODERS.pop("zstd") # pragma: no cover

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ socks = [
5252
"socksio==1.*",
5353
]
5454
zstd = [
55-
"zstandard>=0.18.0",
55+
"backports.zstd>=0.5.0; python_version < '3.14'",
5656
]
5757

5858
[project.scripts]

tests/test_decoders.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
import typing
55
import zlib
66

7+
try:
8+
from compression import zstd
9+
except ImportError:
10+
from backports import zstd
11+
712
import chardet
813
import pytest
9-
import zstandard as zstd
1014

1115
import httpx
1216

0 commit comments

Comments
 (0)