Merge 4d05db33ce into b5addb64f0
This commit is contained in:
commit
573605a6f0
@ -136,7 +136,7 @@ As well as these optional installs:
|
||||
* `rich` - Rich terminal support. *(Optional, with `httpx[cli]`)*
|
||||
* `click` - Command line client support. *(Optional, with `httpx[cli]`)*
|
||||
* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx[brotli]`)*
|
||||
* `zstandard` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)*
|
||||
* `backports.zstd` - Decoding for "zstd" compressed responses on Python before 3.14. *(Optional, with `httpx[zstd]`)*
|
||||
|
||||
A huge amount of credit is due to `requests` for the API layout that
|
||||
much of this work follows, as well as to `urllib3` for plenty of design
|
||||
|
||||
@ -100,8 +100,8 @@ b'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
|
||||
|
||||
Any `gzip` and `deflate` HTTP response encodings will automatically
|
||||
be decoded for you. If `brotlipy` is installed, then the `brotli` response
|
||||
encoding will be supported. If `zstandard` is installed, then `zstd`
|
||||
response encodings will also be supported.
|
||||
encoding will be supported. If the Python version used is 3.14 or higher or
|
||||
if `backports.zstd` is installed, then `zstd` response encodings will also be supported.
|
||||
|
||||
For example, to create an image from binary data returned by a request, you can use the following code:
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ from __future__ import annotations
|
||||
|
||||
import codecs
|
||||
import io
|
||||
import sys
|
||||
import typing
|
||||
import zlib
|
||||
|
||||
@ -28,9 +29,12 @@ except ImportError: # pragma: no cover
|
||||
|
||||
# Zstandard support is optional
|
||||
try:
|
||||
import zstandard
|
||||
if sys.version_info >= (3, 14):
|
||||
from compression import zstd # pragma: no cover
|
||||
else:
|
||||
from backports import zstd # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
zstandard = None # type: ignore
|
||||
zstd = None # type: ignore
|
||||
|
||||
|
||||
class ContentDecoder:
|
||||
@ -162,42 +166,41 @@ class ZStandardDecoder(ContentDecoder):
|
||||
"""
|
||||
Handle 'zstd' RFC 8878 decoding.
|
||||
|
||||
Requires `pip install zstandard`.
|
||||
Requires `pip install backports.zstd` for Python before 3.14.
|
||||
Can be installed as a dependency of httpx using `pip install httpx[zstd]`.
|
||||
"""
|
||||
|
||||
# inspired by the ZstdDecoder implementation in urllib3
|
||||
def __init__(self) -> None:
|
||||
if zstandard is None: # pragma: no cover
|
||||
if zstd is None: # pragma: no cover
|
||||
raise ImportError(
|
||||
"Using 'ZStandardDecoder', ..."
|
||||
"Make sure to install httpx using `pip install httpx[zstd]`."
|
||||
) from None
|
||||
|
||||
self.decompressor = zstandard.ZstdDecompressor().decompressobj()
|
||||
self.decompressor = zstd.ZstdDecompressor()
|
||||
self.seen_data = False
|
||||
|
||||
def decode(self, data: bytes) -> bytes:
|
||||
assert zstandard is not None
|
||||
assert zstd is not None
|
||||
self.seen_data = True
|
||||
output = io.BytesIO()
|
||||
try:
|
||||
output.write(self.decompressor.decompress(data))
|
||||
while self.decompressor.eof and self.decompressor.unused_data:
|
||||
unused_data = self.decompressor.unused_data
|
||||
self.decompressor = zstandard.ZstdDecompressor().decompressobj()
|
||||
self.decompressor = zstd.ZstdDecompressor()
|
||||
output.write(self.decompressor.decompress(unused_data))
|
||||
except zstandard.ZstdError as exc:
|
||||
except zstd.ZstdError as exc:
|
||||
raise DecodingError(str(exc)) from exc
|
||||
return output.getvalue()
|
||||
|
||||
def flush(self) -> bytes:
|
||||
if not self.seen_data:
|
||||
return b""
|
||||
ret = self.decompressor.flush() # note: this is a no-op
|
||||
if not self.decompressor.eof:
|
||||
raise DecodingError("Zstandard data is incomplete") # pragma: no cover
|
||||
return bytes(ret)
|
||||
return b""
|
||||
|
||||
|
||||
class MultiDecoder(ContentDecoder):
|
||||
@ -389,5 +392,5 @@ SUPPORTED_DECODERS = {
|
||||
|
||||
if brotli is None:
|
||||
SUPPORTED_DECODERS.pop("br") # pragma: no cover
|
||||
if zstandard is None:
|
||||
if zstd is None:
|
||||
SUPPORTED_DECODERS.pop("zstd") # pragma: no cover
|
||||
|
||||
@ -52,7 +52,7 @@ socks = [
|
||||
"socksio==1.*",
|
||||
]
|
||||
zstd = [
|
||||
"zstandard>=0.18.0",
|
||||
"backports.zstd>=1.0.0 ; python_version < '3.14'",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import sys
|
||||
import typing
|
||||
import zlib
|
||||
|
||||
import chardet
|
||||
import pytest
|
||||
import zstandard as zstd
|
||||
|
||||
import httpx
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
from compression import zstd # pragma: no cover
|
||||
else:
|
||||
from backports import zstd # pragma: no cover
|
||||
|
||||
|
||||
def test_deflate():
|
||||
"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user