From eacb7581711b59f3008a9d36bf0c05afe8a75dc0 Mon Sep 17 00:00:00 2001 From: John Parton Date: Tue, 29 Aug 2023 18:43:24 -0500 Subject: [PATCH] Remove chardet/charset-normalizer. Add fallback_charset_resolver ClientSession parameter. (#7561) Co-authored-by: Sam Bull (cherry picked from commit 675579699422680607108a7dd68c85ec5284220c) --- .mypy.ini | 3 - CHANGES/7561.feature | 2 + CONTRIBUTORS.txt | 1 + README.rst | 6 +- aiohttp/client.py | 5 ++ aiohttp/client_reqrep.py | 54 ++++++++--------- .../cchardet-unmaintained-admonition.rst | 5 -- docs/client_advanced.rst | 30 ++++++++++ docs/client_reference.rst | 59 +++++++------------ docs/glossary.rst | 16 ----- docs/index.rst | 24 +------- requirements/base.txt | 8 +-- requirements/constraints.txt | 8 +-- requirements/dev.txt | 6 -- requirements/doc-spelling.txt | 2 - requirements/doc.txt | 4 +- requirements/runtime-deps.in | 2 - requirements/runtime-deps.txt | 8 +-- requirements/test.txt | 6 -- setup.cfg | 3 - tests/test_client_response.py | 42 +++---------- 21 files changed, 104 insertions(+), 190 deletions(-) create mode 100644 CHANGES/7561.feature delete mode 100644 docs/_snippets/cchardet-unmaintained-admonition.rst diff --git a/.mypy.ini b/.mypy.ini index 4e6bde442cc..6803a4f3c6d 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -35,9 +35,6 @@ ignore_missing_imports = True [mypy-brotli] ignore_missing_imports = True -[mypy-cchardet] -ignore_missing_imports = True - [mypy-gunicorn.*] ignore_missing_imports = True diff --git a/CHANGES/7561.feature b/CHANGES/7561.feature new file mode 100644 index 00000000000..a57914ff2a3 --- /dev/null +++ b/CHANGES/7561.feature @@ -0,0 +1,2 @@ +Replace automatic character set detection with a `fallback_charset_resolver` parameter +in `ClientSession` to allow user-supplied character set detection functions. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index dc1981120b0..5abdebe2ba5 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -169,6 +169,7 @@ Jesus Cea Jian Zeng Jinkyu Yi Joel Watts +John Parton Jon Nabozny Jonas Krüger Svensson Jonas Obrist diff --git a/README.rst b/README.rst index 44d5fc17c2d..619073aea21 100644 --- a/README.rst +++ b/README.rst @@ -159,22 +159,18 @@ Requirements - async-timeout_ - attrs_ -- charset-normalizer_ - multidict_ - yarl_ - frozenlist_ -Optionally you may install the cChardet_ and aiodns_ libraries (highly -recommended for sake of speed). +Optionally you may install the aiodns_ library (highly recommended for sake of speed). -.. _charset-normalizer: https://pypi.org/project/charset-normalizer .. _aiodns: https://pypi.python.org/pypi/aiodns .. _attrs: https://github.com/python-attrs/attrs .. _multidict: https://pypi.python.org/pypi/multidict .. _frozenlist: https://pypi.org/project/frozenlist/ .. _yarl: https://pypi.python.org/pypi/yarl .. _async-timeout: https://pypi.python.org/pypi/async_timeout -.. _cChardet: https://pypi.python.org/pypi/cchardet License ======= diff --git a/aiohttp/client.py b/aiohttp/client.py index c0eccf2b2fb..db53b58f76a 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -163,6 +163,7 @@ class ClientTimeout: DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60) _RetType = TypeVar("_RetType") +_CharsetResolver = Callable[[ClientResponse, bytes], str] class ClientSession: @@ -194,6 +195,7 @@ class ClientSession: "_read_bufsize", "_max_line_size", "_max_field_size", + "_resolve_charset", ] ) @@ -230,6 +232,7 @@ def __init__( read_bufsize: int = 2**16, max_line_size: int = 8190, max_field_size: int = 8190, + fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8", ) -> None: if loop is None: if connector is not None: @@ -325,6 +328,8 @@ def __init__( for trace_config in self._trace_configs: trace_config.freeze() + self._resolve_charset = fallback_charset_resolver + def __init_subclass__(cls: Type["ClientSession"]) -> None: warnings.warn( "Inheritance class {} from ClientSession " diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 8ac9a08c64c..cdbf4ccc6c6 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -13,6 +13,7 @@ from typing import ( TYPE_CHECKING, Any, + Callable, Dict, Iterable, List, @@ -70,11 +71,6 @@ ssl = None # type: ignore[assignment] SSLContext = object # type: ignore[misc,assignment] -try: - import cchardet as chardet -except ImportError: # pragma: no cover - import charset_normalizer as chardet - __all__ = ("ClientRequest", "ClientResponse", "RequestInfo", "Fingerprint") @@ -742,8 +738,8 @@ class ClientResponse(HeadersMixin): _raw_headers: RawHeaders = None # type: ignore[assignment] _connection = None # current connection - _source_traceback = None - # setted up by ClientRequest after ClientResponse object creation + _source_traceback: Optional[traceback.StackSummary] = None + # set up by ClientRequest after ClientResponse object creation # post-init stage allows to not change ctor signature _closed = True # to allow __del__ for non-initialized properly response _released = False @@ -780,6 +776,15 @@ def __init__( self._loop = loop # store a reference to session #1985 self._session: Optional[ClientSession] = session + # Save reference to _resolve_charset, so that get_encoding() will still + # work after the response has finished reading the body. + if session is None: + # TODO: Fix session=None in tests (see ClientRequest.__init__). + self._resolve_charset: Callable[ + ["ClientResponse", bytes], str + ] = lambda *_: "utf-8" + else: + self._resolve_charset = session._resolve_charset if loop.get_debug(): self._source_traceback = traceback.extract_stack(sys._getframe(1)) @@ -1070,27 +1075,22 @@ def get_encoding(self) -> str: encoding = mimetype.parameters.get("charset") if encoding: - try: - codecs.lookup(encoding) - except LookupError: - encoding = None - if not encoding: - if mimetype.type == "application" and ( - mimetype.subtype == "json" or mimetype.subtype == "rdap" - ): - # RFC 7159 states that the default encoding is UTF-8. - # RFC 7483 defines application/rdap+json - encoding = "utf-8" - elif self._body is None: - raise RuntimeError( - "Cannot guess the encoding of " "a not yet read body" - ) - else: - encoding = chardet.detect(self._body)["encoding"] - if not encoding: - encoding = "utf-8" + with contextlib.suppress(LookupError): + return codecs.lookup(encoding).name + + if mimetype.type == "application" and ( + mimetype.subtype == "json" or mimetype.subtype == "rdap" + ): + # RFC 7159 states that the default encoding is UTF-8. + # RFC 7483 defines application/rdap+json + return "utf-8" + + if self._body is None: + raise RuntimeError( + "Cannot compute fallback encoding of a not yet read body" + ) - return encoding + return self._resolve_charset(self, self._body) async def text(self, encoding: Optional[str] = None, errors: str = "strict") -> str: """Read response payload and decode.""" diff --git a/docs/_snippets/cchardet-unmaintained-admonition.rst b/docs/_snippets/cchardet-unmaintained-admonition.rst deleted file mode 100644 index ec290e0e954..00000000000 --- a/docs/_snippets/cchardet-unmaintained-admonition.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. warning:: - - Note that the :term:`cchardet` project is known not to support - Python 3.10 or higher. See :issue:`6819` and - :gh:`PyYoshi/cChardet/issues/77` for more details. diff --git a/docs/client_advanced.rst b/docs/client_advanced.rst index 1ea9e75974a..037e50a9363 100644 --- a/docs/client_advanced.rst +++ b/docs/client_advanced.rst @@ -653,3 +653,33 @@ are changed so that aiohttp itself can wait on the underlying connection to close. Please follow issue `#1925 `_ for the progress on this. + + +Character Set Detection +----------------------- + +If you encounter a :exc:`UnicodeDecodeError` when using :meth:`ClientResponse.text()` +this may be because the response does not include the charset needed +to decode the body. + +If you know the correct encoding for a request, you can simply specify +the encoding as a parameter (e.g. ``resp.text("windows-1252")``). + +Alternatively, :class:`ClientSession` accepts a ``fallback_charset_resolver`` parameter which +can be used to introduce charset guessing functionality. When a charset is not found +in the Content-Type header, this function will be called to get the charset encoding. For +example, this can be used with the ``chardetng_py`` library.:: + + from chardetng_py import detect + + def charset_resolver(resp: ClientResponse, body: bytes) -> str: + tld = resp.url.host.rsplit(".", maxsplit=1)[-1] + return detect(body, allow_utf8=True, tld=tld) + + ClientSession(fallback_charset_resolver=charset_resolver) + +Or, if ``chardetng_py`` doesn't work for you, then ``charset-normalizer`` is another option:: + + from charset_normalizer import detect + + ClientSession(fallback_charset_resolver=lamba r, b: detect(b)["encoding"] or "utf-8") diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 10ab0233a43..d07c14e26b8 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -51,7 +51,8 @@ The client session supports the context manager protocol for self closing. read_bufsize=2**16, \ requote_redirect_url=True, \ trust_env=False, \ - trace_configs=None) + trace_configs=None, \ + fallback_charset_resolver=lambda r, b: "utf-8") The class for creating client sessions and making requests. @@ -226,6 +227,16 @@ The client session supports the context manager protocol for self closing. disabling. See :ref:`aiohttp-client-tracing-reference` for more information. + :param Callable[[ClientResponse,bytes],str] fallback_charset_resolver: + A :term:`callable` that accepts a :class:`ClientResponse` and the + :class:`bytes` contents, and returns a :class:`str` which will be used as + the encoding parameter to :meth:`bytes.decode()`. + + This function will be called when the charset is not known (e.g. not specified in the + Content-Type header). The default function simply defaults to ``utf-8``. + + .. versionadded:: 3.8.6 + .. attribute:: closed ``True`` if the session has been closed, ``False`` otherwise. @@ -1424,12 +1435,8 @@ Response object Read response's body and return decoded :class:`str` using specified *encoding* parameter. - If *encoding* is ``None`` content encoding is autocalculated - using ``Content-Type`` HTTP header and *charset-normalizer* tool if the - header is not provided by server. - - :term:`cchardet` is used with fallback to :term:`charset-normalizer` if - *cchardet* is not available. + If *encoding* is ``None`` content encoding is determined from the + Content-Type header, or using the ``fallback_charset_resolver`` function. Close underlying connection if data reading gets an error, release connection otherwise. @@ -1438,21 +1445,11 @@ Response object ``None`` for encoding autodetection (default). - :return str: decoded *BODY* - - :raise LookupError: if the encoding detected by cchardet is - unknown by Python (e.g. VISCII). - .. note:: + :raises: :exc:`UnicodeDecodeError` if decoding fails. See also + :meth:`get_encoding`. - If response has no ``charset`` info in ``Content-Type`` HTTP - header :term:`cchardet` / :term:`charset-normalizer` is used for - content encoding autodetection. - - It may hurt performance. If page encoding is known passing - explicit *encoding* parameter might help:: - - await resp.text('ISO-8859-1') + :return str: decoded *BODY* .. method:: json(*, encoding=None, loads=json.loads, \ content_type='application/json') @@ -1460,13 +1457,9 @@ Response object Read response's body as *JSON*, return :class:`dict` using specified *encoding* and *loader*. If data is not still available - a ``read`` call will be done, + a ``read`` call will be done. - If *encoding* is ``None`` content encoding is autocalculated - using :term:`cchardet` or :term:`charset-normalizer` as fallback if - *cchardet* is not available. - - if response's `content-type` does not match `content_type` parameter + If response's `content-type` does not match `content_type` parameter :exc:`aiohttp.ContentTypeError` get raised. To disable content type check pass ``None`` value. @@ -1498,17 +1491,9 @@ Response object .. method:: get_encoding() - Automatically detect content encoding using ``charset`` info in - ``Content-Type`` HTTP header. If this info is not exists or there - are no appropriate codecs for encoding then :term:`cchardet` / - :term:`charset-normalizer` is used. - - Beware that it is not always safe to use the result of this function to - decode a response. Some encodings detected by cchardet are not known by - Python (e.g. VISCII). *charset-normalizer* is not concerned by that issue. - - :raise RuntimeError: if called before the body has been read, - for :term:`cchardet` usage + Retrieve content encoding using ``charset`` info in ``Content-Type`` HTTP header. + If no charset is present or the charset is not understood by Python, the + ``fallback_charset_resolver`` function associated with the ``ClientSession`` is called. .. versionadded:: 3.0 diff --git a/docs/glossary.rst b/docs/glossary.rst index 81bfcfa654b..4bfe7c55126 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -45,22 +45,6 @@ Any object that can be called. Use :func:`callable` to check that. - charset-normalizer - - The Real First Universal Charset Detector. - Open, modern and actively maintained alternative to Chardet. - - https://pypi.org/project/charset-normalizer/ - - cchardet - - cChardet is high speed universal character encoding detector - - binding to charsetdetect. - - https://pypi.python.org/pypi/cchardet/ - - .. include:: _snippets/cchardet-unmaintained-admonition.rst - gunicorn Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for diff --git a/docs/index.rst b/docs/index.rst index 74766113254..f7f913c7152 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,15 +33,6 @@ Library Installation $ pip install aiohttp -You may want to install *optional* :term:`cchardet` library as faster -replacement for :term:`charset-normalizer`: - -.. code-block:: bash - - $ pip install cchardet - -.. include:: _snippets/cchardet-unmaintained-admonition.rst - For speeding up DNS resolving by client API you may install :term:`aiodns` as well. This option is highly recommended: @@ -53,9 +44,9 @@ This option is highly recommended: Installing all speedups in one command -------------------------------------- -The following will get you ``aiohttp`` along with :term:`cchardet`, -:term:`aiodns` and ``Brotli`` in one bundle. No need to type -separate commands anymore! +The following will get you ``aiohttp`` along with :term:`aiodns` and ``Brotli`` in one +bundle. +No need to type separate commands anymore! .. code-block:: bash @@ -158,17 +149,8 @@ Dependencies - *async_timeout* - *attrs* -- *charset-normalizer* - *multidict* - *yarl* -- *Optional* :term:`cchardet` as faster replacement for - :term:`charset-normalizer`. - - Install it explicitly via: - - .. code-block:: bash - - $ pip install cchardet - *Optional* :term:`aiodns` for fast DNS resolving. The library is highly recommended. diff --git a/requirements/base.txt b/requirements/base.txt index 9117a967e3c..1f5a6f50ae2 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with python 3.8 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --output-file=requirements/base.txt --resolver=backtracking --strip-extras requirements/base.in +# pip-compile --allow-unsafe --output-file=requirements/base.txt --strip-extras requirements/base.in # aiodns==3.0.0 ; sys_platform == "linux" or sys_platform == "darwin" # via -r requirements/runtime-deps.in @@ -14,12 +14,8 @@ attrs==23.1.0 # via -r requirements/runtime-deps.in brotli==1.0.9 # via -r requirements/runtime-deps.in -cchardet==2.1.7 ; python_version < "3.10" - # via -r requirements/runtime-deps.in cffi==1.15.1 # via pycares -charset-normalizer==3.2.0 - # via -r requirements/runtime-deps.in frozenlist==1.4.0 # via # -r requirements/runtime-deps.in diff --git a/requirements/constraints.txt b/requirements/constraints.txt index c0197b705f7..bce37fb9016 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -32,9 +32,7 @@ brotli==1.0.9 # via -r requirements/runtime-deps.in build==0.9.0 # via pip-tools -cchardet==2.1.7 ; python_version < "3.10" - # via -r requirements/runtime-deps.in -certifi==2021.10.8 +certifi==2023.7.22 # via requests cffi==1.15.0 # via @@ -42,10 +40,6 @@ cffi==1.15.0 # pycares cfgv==3.3.1 # via pre-commit -charset-normalizer==3.2.0 - # via - # -r requirements/runtime-deps.in - # requests cherry-picker==2.1.0 # via -r requirements/dev.in click==8.0.3 diff --git a/requirements/dev.txt b/requirements/dev.txt index 3920fe45df6..addae3f605f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -30,8 +30,6 @@ brotli==1.0.9 # via -r requirements/runtime-deps.in build==0.10.0 # via pip-tools -cchardet==2.1.7 ; python_version < "3.10" - # via -r requirements/runtime-deps.in certifi==2023.7.22 # via requests cffi==1.15.1 @@ -40,10 +38,6 @@ cffi==1.15.1 # pycares cfgv==3.3.1 # via pre-commit -charset-normalizer==3.2.0 - # via - # -r requirements/runtime-deps.in - # requests cherry-picker==2.1.0 # via -r requirements/dev.in click==8.1.6 diff --git a/requirements/doc-spelling.txt b/requirements/doc-spelling.txt index 4b2a57f79d6..d28b38c3516 100644 --- a/requirements/doc-spelling.txt +++ b/requirements/doc-spelling.txt @@ -14,8 +14,6 @@ blockdiag==3.0.0 # via sphinxcontrib-blockdiag certifi==2023.7.22 # via requests -charset-normalizer==3.2.0 - # via requests click==8.1.6 # via # click-default-group diff --git a/requirements/doc.txt b/requirements/doc.txt index d00e98479d4..a6c9d5aa809 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -14,9 +14,7 @@ blockdiag==3.0.0 # via sphinxcontrib-blockdiag certifi==2023.7.22 # via requests -charset-normalizer==3.2.0 - # via requests -click==7.1.2 +click==8.1.6 # via # click-default-group # towncrier diff --git a/requirements/runtime-deps.in b/requirements/runtime-deps.in index aacdefd34bd..2d782c551e7 100644 --- a/requirements/runtime-deps.in +++ b/requirements/runtime-deps.in @@ -1,7 +1,6 @@ # Extracted from `setup.cfg` via `make sync-direct-runtime-deps` attrs >= 17.3.0 -charset-normalizer >=2.0, < 4.0 multidict >=4.5, < 7.0 async-timeout >= 4.0, < 5.0 ; python_version < "3.11" yarl >= 1.0, < 2.0 @@ -9,4 +8,3 @@ frozenlist >= 1.1.1 aiosignal >= 1.1.2 aiodns; sys_platform=="linux" or sys_platform=="darwin" Brotli -cchardet; python_version < "3.10" diff --git a/requirements/runtime-deps.txt b/requirements/runtime-deps.txt index b2d61dd9661..a0521ba2de1 100644 --- a/requirements/runtime-deps.txt +++ b/requirements/runtime-deps.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with python 3.8 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --output-file=requirements/runtime-deps.txt --resolver=backtracking --strip-extras requirements/runtime-deps.in +# pip-compile --allow-unsafe --output-file=requirements/runtime-deps.txt --strip-extras requirements/runtime-deps.in # aiodns==3.0.0 ; sys_platform == "linux" or sys_platform == "darwin" # via -r requirements/runtime-deps.in @@ -14,12 +14,8 @@ attrs==23.1.0 # via -r requirements/runtime-deps.in brotli==1.0.9 # via -r requirements/runtime-deps.in -cchardet==2.1.7 ; python_version < "3.10" - # via -r requirements/runtime-deps.in cffi==1.15.1 # via pycares -charset-normalizer==3.2.0 - # via -r requirements/runtime-deps.in frozenlist==1.4.0 # via # -r requirements/runtime-deps.in diff --git a/requirements/test.txt b/requirements/test.txt index d926a98e87c..fc8b3a22e8c 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -16,18 +16,12 @@ attrs==23.1.0 # via -r requirements/runtime-deps.in brotli==1.0.9 # via -r requirements/runtime-deps.in -cchardet==2.1.7 ; python_version < "3.10" - # via -r requirements/runtime-deps.in certifi==2023.7.22 # via requests cffi==1.15.1 # via # cryptography # pycares -charset-normalizer==3.2.0 - # via - # -r requirements/runtime-deps.in - # requests click==8.1.6 # via # typer diff --git a/setup.cfg b/setup.cfg index a3ad1b1c5f2..19388fb3d25 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,7 +48,6 @@ include_package_data = True install_requires = attrs >= 17.3.0 - charset-normalizer >=2.0, < 4.0 multidict >=4.5, < 7.0 async-timeout >= 4.0, < 5.0 ; python_version < "3.11" yarl >= 1.0, < 2.0 @@ -65,8 +64,6 @@ speedups = # required c-ares (aiodns' backend) will not build on windows aiodns; sys_platform=="linux" or sys_platform=="darwin" Brotli - # cchardet is unmaintained: aio-libs/aiohttp#6819 - cchardet; python_version < "3.10" [options.packages.find] exclude = diff --git a/tests/test_client_response.py b/tests/test_client_response.py index f8bee42be49..7d07f13e46c 100644 --- a/tests/test_client_response.py +++ b/tests/test_client_response.py @@ -440,7 +440,9 @@ def side_effect(*args, **kwargs): assert not response.get_encoding.called -async def test_text_detect_encoding(loop, session) -> None: +@pytest.mark.parametrize("content_type", ("text/plain", "text/plain;charset=invalid")) +async def test_text_charset_resolver(content_type: str, loop, session) -> None: + session._resolve_charset = lambda r, b: "cp1251" response = ClientResponse( "get", URL("http://def-cl-resp.org"), @@ -458,7 +460,7 @@ def side_effect(*args, **kwargs): fut.set_result('{"тест": "пройден"}'.encode("cp1251")) return fut - response._headers = {"Content-Type": "text/plain"} + response._headers = {"Content-Type": content_type} content = response.content = mock.Mock() content.read.side_effect = side_effect @@ -466,35 +468,7 @@ def side_effect(*args, **kwargs): res = await response.text() assert res == '{"тест": "пройден"}' assert response._connection is None - - -async def test_text_detect_encoding_if_invalid_charset(loop, session) -> None: - response = ClientResponse( - "get", - URL("http://def-cl-resp.org"), - request_info=mock.Mock(), - writer=mock.Mock(), - continue100=None, - timer=TimerNoop(), - traces=[], - loop=loop, - session=session, - ) - - def side_effect(*args, **kwargs): - fut = loop.create_future() - fut.set_result('{"тест": "пройден"}'.encode("cp1251")) - return fut - - response._headers = {"Content-Type": "text/plain;charset=invalid"} - content = response.content = mock.Mock() - content.read.side_effect = side_effect - - await response.read() - res = await response.text() - assert res == '{"тест": "пройден"}' - assert response._connection is None - assert response.get_encoding().lower() in ("windows-1251", "maccyrillic") + assert response.get_encoding() == "cp1251" async def test_get_encoding_body_none(loop, session) -> None: @@ -521,7 +495,7 @@ def side_effect(*args, **kwargs): with pytest.raises( RuntimeError, - match="^Cannot guess the encoding of a not yet read body$", + match="^Cannot compute fallback encoding of a not yet read body$", ): response.get_encoding() assert response.closed @@ -742,9 +716,7 @@ def test_get_encoding_unknown(loop, session) -> None: ) response._headers = {"Content-Type": "application/json"} - with mock.patch("aiohttp.client_reqrep.chardet") as m_chardet: - m_chardet.detect.return_value = {"encoding": None} - assert response.get_encoding() == "utf-8" + assert response.get_encoding() == "utf-8" def test_raise_for_status_2xx() -> None: