From 813fd9f1ac424229f8614346de8a20739481b395 Mon Sep 17 00:00:00 2001 From: Rui Xi Date: Tue, 25 Nov 2025 15:06:55 +0800 Subject: [PATCH] Restore brotlicffi support (#7160) * restore brotlicffi dependency * pre-commit --- .../downloadermiddlewares/httpcompression.py | 11 +++++---- scrapy/utils/_compression.py | 5 +++- ...st_downloadermiddleware_httpcompression.py | 24 +++++++++++++------ tox.ini | 6 +++-- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/scrapy/downloadermiddlewares/httpcompression.py b/scrapy/downloadermiddlewares/httpcompression.py index fd508f759..e81888d9b 100644 --- a/scrapy/downloadermiddlewares/httpcompression.py +++ b/scrapy/downloadermiddlewares/httpcompression.py @@ -31,7 +31,10 @@ logger = getLogger(__name__) ACCEPTED_ENCODINGS: list[bytes] = [b"gzip", b"deflate"] try: - import brotli + try: + import brotli + except ImportError: + import brotlicffi as brotli except ImportError: pass else: @@ -40,8 +43,8 @@ else: except AttributeError: # pragma: no cover warnings.warn( "You have brotli installed. But 'br' encoding support now requires " - "brotli version >= 1.2.0. Please upgrade brotli version to make Scrapy " - "decode 'br' encoded responses.", + "brotli's or brotlicffi's version >= 1.2.0. Please upgrade " + "brotli/brotlicffi to make Scrapy decode 'br' encoded responses.", ) else: ACCEPTED_ENCODINGS.append(b"br") @@ -208,7 +211,7 @@ class HttpCompressionMiddleware: f"from unsupported encoding(s) '{encodings_str}'." ) if b"br" in encodings: - msg += " You need to install brotli >= 1.2.0 to decode 'br'." + msg += " You need to install brotli or brotlicffi >= 1.2.0 to decode 'br'." if b"zstd" in encodings: msg += " You need to install zstandard to decode 'zstd'." logger.warning(msg) diff --git a/scrapy/utils/_compression.py b/scrapy/utils/_compression.py index 0a8191cc6..4767c29f2 100644 --- a/scrapy/utils/_compression.py +++ b/scrapy/utils/_compression.py @@ -3,7 +3,10 @@ import zlib from io import BytesIO with contextlib.suppress(ImportError): - import brotli + try: + import brotli + except ImportError: + import brotlicffi as brotli with contextlib.suppress(ImportError): import zstandard diff --git a/tests/test_downloadermiddleware_httpcompression.py b/tests/test_downloadermiddleware_httpcompression.py index 2080b3806..bb7fcd6c7 100644 --- a/tests/test_downloadermiddleware_httpcompression.py +++ b/tests/test_downloadermiddleware_httpcompression.py @@ -52,9 +52,14 @@ FORMAT = { def _skip_if_no_br() -> None: try: - import brotli # noqa: PLC0415 + try: + import brotli # noqa: PLC0415 - brotli.Decompressor.can_accept_more_data + brotli.Decompressor.can_accept_more_data + except (ImportError, AttributeError): + import brotlicffi # noqa: PLC0415 + + brotlicffi.Decompressor.can_accept_more_data except (ImportError, AttributeError): pytest.skip("no brotli support") @@ -152,9 +157,14 @@ class TestHttpCompression: def test_process_response_br_unsupported(self): try: - import brotli # noqa: F401,PLC0415 + try: + import brotli # noqa: F401,PLC0415 - pytest.skip("Requires not having brotli support") + pytest.skip("Requires not having brotli support") + except ImportError: + import brotlicffi # noqa: F401,PLC0415 + + pytest.skip("Requires not having brotli support") except ImportError: pass response = self._getresponse("br") @@ -171,9 +181,9 @@ class TestHttpCompression: "scrapy.downloadermiddlewares.httpcompression", "WARNING", ( - "HttpCompressionMiddleware cannot decode the response for" - " http://scrapytest.org/ from unsupported encoding(s) 'br'." - " You need to install brotli >= 1.2.0 to decode 'br'." + "HttpCompressionMiddleware cannot decode the response for " + "http://scrapytest.org/ from unsupported encoding(s) 'br'. " + "You need to install brotli or brotlicffi >= 1.2.0 to decode 'br'." ), ), ) diff --git a/tox.ini b/tox.ini index ad5f17595..5200b26e0 100644 --- a/tox.ini +++ b/tox.ini @@ -131,7 +131,8 @@ deps = Twisted[http2] boto3 bpython # optional for shell wrapper tests - brotli >= 1.2.0 # optional for HTTP compress downloader middleware tests + brotli >= 1.2.0; implementation_name != "pypy" # optional for HTTP compress downloader middleware tests + brotlicffi >= 1.2.0.0; implementation_name == "pypy" # optional for HTTP compress downloader middleware tests google-cloud-storage ipython robotexclusionrulesparser @@ -145,7 +146,8 @@ deps = Pillow==8.3.2 boto3==1.20.0 bpython==0.7.1 - brotli==1.2.0 + brotli==1.2.0; implementation_name != "pypy" + brotlicffi==1.2.0.0; implementation_name == "pypy" google-cloud-storage==1.29.0 ipython==7.1.0 robotexclusionrulesparser==1.6.2