From 555b870eb19d497ddb67042645420083ec8efb02 Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Tue, 14 May 2024 14:59:26 -0700 Subject: [PATCH] Allow character detection dependencies to be optional in post-packaging steps --- .github/workflows/run-tests.yml | 20 ++++++++++++++++++++ src/requests/__init__.py | 6 +++++- src/requests/compat.py | 25 ++++++++++++++++++++----- src/requests/models.py | 7 ++++++- src/requests/packages.py | 25 +++++++++---------------- 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e1699fe81a..c35af968c4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -37,3 +37,23 @@ jobs: - name: Run tests run: | make ci + + no_chardet: + name: "No Character Detection" + runs-on: ubuntu-latest + strategy: + fail-fast: true + + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - name: 'Set up Python 3.8' + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d + with: + python-version: '3.8' + - name: Install dependencies + run: | + make + python -m pip uninstall -y "charset_normalizer" "chardet" + - name: Run tests + run: | + make ci diff --git a/src/requests/__init__.py b/src/requests/__init__.py index 300a16c574..051cda1340 100644 --- a/src/requests/__init__.py +++ b/src/requests/__init__.py @@ -83,7 +83,11 @@ def check_compatibility(urllib3_version, chardet_version, charset_normalizer_ver # charset_normalizer >= 2.0.0 < 4.0.0 assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0) else: - raise Exception("You need either charset_normalizer or chardet installed") + warnings.warn( + "Unable to find acceptable character detection dependency " + "(chardet or charset_normalizer).", + RequestsDependencyWarning, + ) def _check_cryptography(cryptography_version): diff --git a/src/requests/compat.py b/src/requests/compat.py index 6776163c94..095de1b6ca 100644 --- a/src/requests/compat.py +++ b/src/requests/compat.py @@ -7,13 +7,28 @@ compatibility until the next major version. """ -try: - import chardet -except ImportError: - import charset_normalizer as chardet - +import importlib import sys +# ------------------- +# Character Detection +# ------------------- + + +def _resolve_char_detection(): + """Find supported character detection libraries.""" + chardet = None + for lib in ("chardet", "charset_normalizer"): + if chardet is None: + try: + chardet = importlib.import_module(lib) + except ImportError: + pass + return chardet + + +chardet = _resolve_char_detection() + # ------- # Pythons # ------- diff --git a/src/requests/models.py b/src/requests/models.py index 44556394ec..8f56ca7d23 100644 --- a/src/requests/models.py +++ b/src/requests/models.py @@ -789,7 +789,12 @@ def next(self): @property def apparent_encoding(self): """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" - return chardet.detect(self.content)["encoding"] + if chardet is not None: + return chardet.detect(self.content)["encoding"] + else: + # If no character detection library is available, we'll fall back + # to a standard Python utf-8 str. + return "utf-8" def iter_content(self, chunk_size=1, decode_unicode=False): """Iterates over the response data. When stream=True is set on the diff --git a/src/requests/packages.py b/src/requests/packages.py index a9e5ae087d..5ab3d8e250 100644 --- a/src/requests/packages.py +++ b/src/requests/packages.py @@ -1,13 +1,6 @@ import sys -try: - import chardet -except ImportError: - import warnings - - import charset_normalizer as chardet - - warnings.filterwarnings("ignore", "Trying to detect", module="charset_normalizer") +from .compat import chardet # This code exists for backwards compatibility reasons. # I don't like it either. Just look the other way. :) @@ -20,11 +13,11 @@ if mod == package or mod.startswith(f"{package}."): sys.modules[f"requests.packages.{mod}"] = sys.modules[mod] -target = chardet.__name__ -for mod in list(sys.modules): - if mod == target or mod.startswith(f"{target}."): - imported_mod = sys.modules[mod] - sys.modules[f"requests.packages.{mod}"] = imported_mod - mod = mod.replace(target, "chardet") - sys.modules[f"requests.packages.{mod}"] = imported_mod -# Kinda cool, though, right? +if chardet is not None: + target = chardet.__name__ + for mod in list(sys.modules): + if mod == target or mod.startswith(f"{target}."): + imported_mod = sys.modules[mod] + sys.modules[f"requests.packages.{mod}"] = imported_mod + mod = mod.replace(target, "chardet") + sys.modules[f"requests.packages.{mod}"] = imported_mod