From 555b870eb19d497ddb67042645420083ec8efb02 Mon Sep 17 00:00:00 2001
From: Nate Prewitt <nate.prewitt@gmail.com>
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