Skip to content

Commit 6c51b94

Browse files
committed
1 parent 3fee278 commit 6c51b94

File tree

8 files changed

+30
-21
lines changed

8 files changed

+30
-21
lines changed

.github/workflows/test-suite.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ jobs:
1414

1515
strategy:
1616
matrix:
17-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
17+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
1818

1919
steps:
20-
- uses: "actions/checkout@v4"
21-
- uses: "actions/setup-python@v5"
20+
- uses: actions/checkout@v5
21+
- uses: actions/setup-python@v6
2222
with:
2323
python-version: "${{ matrix.python-version }}"
2424
allow-prereleases: true
@@ -30,5 +30,6 @@ jobs:
3030
run: "scripts/build"
3131
- name: "Run tests"
3232
run: "scripts/test"
33+
timeout-minutes: 10 # TODO(@cclauss): Remove once Python 3.14 tests are passing.
3334
- name: "Enforce coverage"
3435
run: "scripts/coverage"

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: 16 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
3436

3537

3638
class ContentDecoder:
@@ -162,32 +164,34 @@ 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
180+
self.decompressor.eof = None
181+
self.decompressor.unused_data = None
178182
self.seen_data = False
179183

180184
def decode(self, data: bytes) -> bytes:
181-
assert zstandard is not None
185+
assert zstd is not None
182186
self.seen_data = True
183187
output = io.BytesIO()
184188
try:
185189
output.write(self.decompressor.decompress(data))
186190
while self.decompressor.eof and self.decompressor.unused_data:
187191
unused_data = self.decompressor.unused_data
188-
self.decompressor = zstandard.ZstdDecompressor().decompressobj()
192+
self.decompressor = zstd
189193
output.write(self.decompressor.decompress(unused_data))
190-
except zstandard.ZstdError as exc:
194+
except zstd.ZstdError as exc:
191195
raise DecodingError(str(exc)) from exc
192196
return output.getvalue()
193197

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

390394
if brotli is None:
391395
SUPPORTED_DECODERS.pop("br") # pragma: no cover
392-
if zstandard is None:
396+
if zstd is None:
393397
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]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ twine==6.1.0
2121
coverage[toml]==7.10.6
2222
cryptography==45.0.7
2323
mypy==1.17.1
24-
pytest==8.4.1
24+
pytest==8.4.2
2525
ruff==0.12.11
2626
trio==0.30.0
2727
trio-typing==0.10.0

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)