← oracle_full

requests_7505

resolved RESOLVED UNSUBMITTED PASS · None tool calls · 0 s · psf/requests

Task input

Add hasattr checks for remaining protocol isinstance checks

This PR is a successor to #7502. The proxied-read case appears in the standard library which points to it being a pretty well established pattern. A quick search shows there are two other places this was missed in.

Rather than put `or hasattr(data, "read")` everywhere we use want to use `SupportsRead`, this combines it into a single check that scopes typing correctly for proxy objects. That should hopefully avoid missing it for future checks as well.

Tool calls (0)

#ToolArgumentsResult
No trace captured.

Patch

--- a/src/requests/_types.py
+++ b/src/requests/_types.py
@@ -29,6 +29,11 @@ class SupportsRead(Protocol[_T_co]):
     def read(self, length: int = ..., /) -> _T_co: ...
 
 
+def has_read(obj: Any) -> TypeIs[SupportsRead[str | bytes]]:
+    """Check if obj supports read, including __getattr__ based proxies."""
+    return isinstance(obj, SupportsRead) or hasattr(obj, "read")
+
+
 @runtime_checkable
 class SupportsItems(Protocol[_KT_co, _VT_co]):
     def items(self) -> Iterable[tuple[_KT_co, _VT_co]]: ...
--- a/src/requests/models.py
+++ b/src/requests/models.py
@@ -35,8 +35,8 @@
 from urllib3.filepost import encode_multipart_formdata
 from urllib3.util import parse_url
 
+from . import _types as _t
 from ._internal_utils import to_native_string, unicode_is_ascii
-from ._types import SupportsRead as _SupportsRead
 from .auth import HTTPBasicAuth
 from .compat import (
     JSONDecodeError,
@@ -87,7 +87,6 @@
 
     from typing_extensions import Self
 
-    from . import _types as _t
     from .adapters import HTTPAdapter
     from .cookies import RequestsCookieJar
 
@@ -161,7 +160,7 @@ def _encode_params(
 
         if isinstance(data, (str, bytes)):
             return data
-        elif isinstance(data, _SupportsRead):
+        elif _t.has_read(data):
             return data
         elif hasattr(data, "__iter__"):
             result: list[tuple[bytes, bytes]] = []
@@ -236,9 +235,7 @@ def _encode_files(
 
             if isinstance(fp, (str, bytes, bytearray)):
                 fdata = fp
-            # data that proxies attributes to underlying objects needs hasattr
-            # defensive check for untyped callers
-            elif isinstance(fp, _SupportsRead) or hasattr(fp, "read"):
+            elif _t.has_read(fp):
                 fdata = fp.read()
             elif fp is None:  # defensive check for untyped callers
                 continue
@@ -641,7 +638,7 @@ def prepare_body(
             else:
                 if raw_data:
                     body = self._encode_params(raw_data)
-                    if isinstance(data, basestring) or isinstance(data, _SupportsRead):
+                    if isinstance(data, basestring) or _t.has_read(data):
                         content_type = None
                     else:
                         content_type = "application/x-www-form-urlencoded"

Test output

show
........................................................................ [ 21%]
.................................................s.pytest-httpbin server hit an exception serving request: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:1032)
attempting to ignore so the rest of the tests can run
..................... [ 42%]
........................................................................ [ 63%]
..........................x............................................. [ 84%]
.....................................................                    [100%]
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/requests/lib/python3.13/site-packages/_pytest/config/__init__.py:1464
  /Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/requests/lib/python3.13/site-packages/_pytest/config/__init__.py:1464: PytestConfigWarning: Unknown config option: timeout
  
    self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")

tests/test_requests.py::TestRequests::test_set_basicauth[42-42]
tests/test_requests.py::TestRequests::test_set_basicauth[42-42]
  /tmp/swe_work/oracle_full/requests_7505/b/workspace/src/requests/auth.py:45: DeprecationWarning: Non-string usernames will no longer be supported in Requests 3.0.0. Please convert the object you've passed in (42) to a string or bytes object in the near future to avoid problems.
    warnings.warn(

tests/test_requests.py::TestRequests::test_set_basicauth[42-42]
tests/test_requests.py::TestRequests::test_set_basicauth[42-42]
  /tmp/swe_work/oracle_full/requests_7505/b/workspace/src/requests/auth.py:55: DeprecationWarning: Non-string passwords will no longer be supported in Requests 3.0.0. Please convert the object you've passed in (<class 'int'>) to a string or bytes object in the near future to avoid problems.
    warnings.warn(

tests/test_requests.py::TestRequests::test_set_basicauth[None-None]
tests/test_requests.py::TestRequests::test_set_basicauth[None-None]
  /tmp/swe_work/oracle_full/requests_7505/b/workspace/src/requests/auth.py:45: DeprecationWarning: Non-string usernames will no longer be supported in Requests 3.0.0. Please convert the object you've passed in (None) to a string or bytes object in the near future to avoid problems.
    warnings.warn(

tests/test_requests.py::TestRequests::test_set_basicauth[None-None]
tests/test_requests.py::TestRequests::test_set_basicauth[None-None]
  /tmp/swe_work/oracle_full/requests_7505/b/workspace/src/requests/auth.py:55: DeprecationWarning: Non-string passwords will no longer be supported in Requests 3.0.0. Please convert the object you've passed in (<class 'NoneType'>) to a string or bytes object in the near future to avoid problems.
    warnings.warn(

tests/test_requests.py::TestPreparingURLs::test_different_connection_pool_for_tls_settings_verify_True
tests/test_requests.py::TestPreparingURLs::test_different_connection_pool_for_tls_settings_verify_bundle_expired_cert
tests/test_requests.py::TestPreparingURLs::test_different_connection_pool_for_tls_settings_verify_bundle_unexpired_cert
tests/test_requests.py::TestPreparingURLs::test_different_connection_pool_for_mtls_settings
  /Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/requests/lib/python3.13/site-packages/urllib3/connectionpool.py:1129: InsecureRequestWarning: Unverified HTTPS request is being made to host 'localhost'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
    warnings.warn(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
339 passed, 1 skipped, 1 xfailed, 13 warnings in 39.00s
[2026-09-23 18:20:39,982] WARNING in core: flasgger is not installed; serving the static landing page at / and skipping the Swagger UI and /spec.json.
127.0.0.1 - - [23/Sep/2026 18:20:40] "GET /get HTTP/1.1" 200 208
127.0.0.1 - - [23/Sep/2026 18:20:40] "GET /get HTTP/1.1" 200 272