← eval8_submission_v1a

requests_7433

resolved RESOLVED PASS · 8 tool calls · 90 s · psf/requests

Task input

Fix `prepare_body` stream detection for `__getattr__`-based file wrappers

Resolves #7432 .

Add `hasattr(data, "__iter__")` as a fallback stream detector:
```python
if (
    isinstance(data, Iterable) or hasattr(data, "__iter__")
) and not isinstance(data, (str, bytes, list, tuple, Mapping)):
```
This catches file-like objects that proxy their interface through `__getattr__`, restoring the 2.33.1 behavior without reverting the isinstance(Iterable) modernization for standard iterables.

Tool calls (9)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"prepare_body\" src/requests | head -20"
}
{"status": "ok", "stdout": "src/requests/models.py:442: self.prepare_body(data, files, json)\nsrc/requests/models.py:574: def prepare_body(\n", "stderr": "", "e
full
{"status": "ok", "stdout": "src/requests/models.py:442:        self.prepare_body(data, files, json)\nsrc/requests/models.py:574:    def prepare_body(\n", "stderr": "", "exit_code": 0}
2run_command
{
 "command": "sed -n '574,620p' src/requests/models.py"
}
{"status": "ok", "stdout": " def prepare_body(\n self, data: _t.DataType, files: _t.FilesType, json: _t.JsonType = None\n ) -> None:\n \"\"\"Prepares the given
full
{"status": "ok", "stdout": "    def prepare_body(\n        self, data: _t.DataType, files: _t.FilesType, json: _t.JsonType = None\n    ) -> None:\n        \"\"\"Prepares the given HTTP body data.\"\"\"\n\n        # Check if file, fo, generator, iterator.\n        # If not, run through normal process.\n\n        # Nottin' on you.\n        body = None\n        content_type = None\n\n        if not data and json is not None:\n            # urllib3 requires a bytes-like body. Python 2's json.dumps\n            # provides this natively, but Python 3 gives a Unicode string.\n            content_type = \"application/json\"\n\n            try:\n                body = complexjson.dumps(json, allow_nan=False)\n            except ValueError as ve:\n                raise InvalidJSONError(ve, request=self)\n\n            if not isinstance(body, bytes):\n                body = body.encode(\"utf-8\")\n\n        if isinstance(data, Iterable) and not isinstance(\n            data, (str, bytes, list, tuple, Mapping)\n        ):\n            try:\n                length = super_len(data)\n            except (TypeError, AttributeError, UnsupportedOperation):\n                length = None\n\n            body = data\n\n            if getattr(body, \"tell\", None) is not None:\n                # Record the current file position before reading.\n                # This will allow us to rewind a file in the event\n                # of a redirect.\n                try:\n                    self._body_position = body.tell()  # type: ignore[union-attr]  # guarded by getattr check\n                except OSError:\n                    # This differentiates from None, allowing us to catch\n                    # a failed `tell()` later when trying to rewind the body\n                    self._body_position = object()\n\n            if files:\n", "stderr": "", "exit_code": 0}
3run_command
{
 "command": "grep -n \"from collections.abc import Iterable\" src/requests/models.py || grep -n \"from typing import Iterable\" src/requests/models.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
full
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
4run_command
{
 "command": "sed -n '1,100p' src/requests/models.py"
}
{"status": "ok", "stdout": "\"\"\"\nrequests.models\n~~~~~~~~~~~~~~~\n\nThis module contains the primary objects that power Requests.\n\"\"\"\n\nfrom __future__
full
{"status": "ok", "stdout": "\"\"\"\nrequests.models\n~~~~~~~~~~~~~~~\n\nThis module contains the primary objects that power Requests.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\n\n# Import encoding now, to avoid implicit import later.\n# Implicit import within threads may cause LookupError when standard library is in a ZIP,\n# such as in Embedded Python. See https://github.com/psf/requests/issues/3578.\nimport encodings.idna  # noqa: F401  # type: ignore[reportUnusedImport]\nfrom collections.abc import Callable, Generator, Iterable, Iterator, Mapping\nfrom io import UnsupportedOperation\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Final,\n    Literal,\n    cast,\n    overload,\n)\n\nfrom urllib3.exceptions import (\n    DecodeError,\n    LocationParseError,\n    ProtocolError,\n    ReadTimeoutError,\n    SSLError,\n)\nfrom urllib3.fields import RequestField\nfrom urllib3.filepost import encode_multipart_formdata\nfrom urllib3.util import parse_url\n\nfrom ._internal_utils import to_native_string, unicode_is_ascii\nfrom ._types import SupportsRead as _SupportsRead\nfrom .auth import HTTPBasicAuth\nfrom .compat import (\n    JSONDecodeError,\n    basestring,\n    builtin_str,\n    chardet,\n    cookielib,\n    urlencode,\n    urlsplit,\n    urlunparse,\n)\nfrom .compat import json as complexjson\nfrom .cookies import (\n    _copy_cookie_jar,  # type: ignore[reportPrivateUsage]\n    cookiejar_from_dict,\n    get_cookie_header,\n)\nfrom .exceptions import (\n    ChunkedEncodingError,\n    ConnectionError,\n    ContentDecodingError,\n    HTTPError,\n    InvalidJSONError,\n    InvalidURL,\n    MissingSchema,\n    StreamConsumedError,\n)\nfrom .exceptions import JSONDecodeError as RequestsJSONDecodeError\nfrom .exceptions import SSLError as RequestsSSLError\nfrom .hooks import default_hooks\nfrom .status_codes import codes\nfrom .structures import CaseInsensitiveDict\nfrom .utils import (\n    check_header_validity,\n    get_auth_from_url,
5edit_file
{
 "filepath": "src/requests/models.py",
 "new_string": "        if (isinstance(data, Iterable) or hasattr(data, \"__iter__\")) and not isinstance(\n            data, (str, bytes, list, tuple, Mapping)\n        ):",
 "old_string": "        if isinstance(data, Iterable) and not isinstance(\n            data, (str, bytes, list, tuple, Mapping)\n        ):"
}
{"status": "ok", "filepath": "src/requests/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/requests/models.py\n+++ b/src/requests/models.p
full
{"status": "ok", "filepath": "src/requests/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/src/requests/models.py\n+++ b/src/requests/models.py\n@@ -596,7 +596,7 @@\n             if not isinstance(body, bytes):\n                 body = body.encode(\"utf-8\")\n \n-        if isinstance(data, Iterable) and not isinstance(\n+        if (isinstance(data, Iterable) or hasattr(data, \"__iter__\")) and not isinstance(\n             data, (str, bytes, list, tuple, Mapping)\n         ):\n             try:\n", "is_truncated": false}
6write_file
{
 "content": "\nimport requests\nfrom collections.abc import Iterable\n\nclass ProxyIterable:\n    def __getattr__(self, name):\n        if name == \"__iter__\":\n            return iter([1, 2, 3])\n        raise AttributeError(name)\n\ndef test_proxy_iterable():\n    data = ProxyIterable()\n    # This should be treated as a stream (Iterable)\n    # In requests, if it's an iterable and not (str, bytes, list, tuple, Mapping),\n    # it's handled as a stream in prepare_body.\n    \n    # We can verify this by checking if it's treated as a stream by creating a Request\n    # and calling prepare_body (which is called by prepare())\n    req = requests.Request('POST', 'http://httpbin.org/post', data=data)\n    prepared = req.prepare()\n    \n    # If it was treated as a stream, prepared.body should be the data object itself\n    # (or at least not converted to a string/bytes via urlencode)\n    assert prepared.body == data\n    print(\"Test passed!\")\n\nif __name__ == \"__main__\":\n    test_proxy_iterable()\n",
 "filepath": "/tmp/test_proxy_iterable.py"
}
{"status": "error", "error_type": "FileWriteError", "error_message": "Path traversal detected: '/tmp/test_proxy_iterable.py' escapes workspace root."}
full
{"status": "error", "error_type": "FileWriteError", "error_message": "Path traversal detected: '/tmp/test_proxy_iterable.py' escapes workspace root."}
7write_file
{
 "content": "\nimport requests\nfrom collections.abc import Iterable\n\nclass ProxyIterable:\n    def __getattr__(self, name):\n        if name == \"__iter__\":\n            return iter([1, 2, 3])\n        raise AttributeError(name)\n\ndef test_proxy_iterable():\n    data = ProxyIterable()\n    # This should be treated as a stream (Iterable)\n    # In requests, if it's an iterable and not (str, bytes, list, tuple, Mapping),\n    # it's handled as a stream in prepare_body.\n    \n    # We can verify this by checking if it's treated as a stream by creating a Request\n    # and calling prepare_body (which is called by prepare())\n    req = requests.Request('POST', 'http://httpbin.org/post', data=data)\n    prepared = req.prepare()\n    \n    # If it was treated as a stream, prepared.body should be the data object itself\n    # (or at least not converted to a string/bytes via urlencode)\n    assert prepared.body == data\n    print(\"Test passed!\")\n\nif __name__ == \"__main__\":\n    test_proxy_iterable()\n",
 "filepath": "test_proxy_iterable.py"
}
{"status": "ok", "filepath": "test_proxy_iterable.py", "size": 972}
full
{"status": "ok", "filepath": "test_proxy_iterable.py", "size": 972}
8run_command
{
 "command": "python3 test_proxy_iterable.py"
}
{"status": "ok", "stdout": "Test passed!\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Test passed!\n", "stderr": "", "exit_code": 0}
9submit_patch
{}
{"status": "ok", "patch_size": 1727, "files_changed": 2}
full
{"status": "ok", "patch_size": 1727, "files_changed": 2}

Patch

diff --git a/src/requests/models.py b/src/requests/models.py
index 4142f2a4..f2b6b318 100644
--- a/src/requests/models.py
+++ b/src/requests/models.py
@@ -596,7 +596,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
             if not isinstance(body, bytes):
                 body = body.encode("utf-8")
 
-        if isinstance(data, Iterable) and not isinstance(
+        if (isinstance(data, Iterable) or hasattr(data, "__iter__")) and not isinstance(
             data, (str, bytes, list, tuple, Mapping)
         ):
             try:
diff --git a/test_proxy_iterable.py b/test_proxy_iterable.py
new file mode 100644
index 00000000..1eca1b3a
--- /dev/null
+++ b/test_proxy_iterable.py
@@ -0,0 +1,28 @@
+
+import requests
+from collections.abc import Iterable
+
+class ProxyIterable:
+    def __getattr__(self, name):
+        if name == "__iter__":
+            return iter([1, 2, 3])
+        raise AttributeError(name)
+
+def test_proxy_iterable():
+    data = ProxyIterable()
+    # This should be treated as a stream (Iterable)
+    # In requests, if it's an iterable and not (str, bytes, list, tuple, Mapping),
+    # it's handled as a stream in prepare_body.
+    
+    # We can verify this by checking if it's treated as a stream by creating a Request
+    # and calling prepare_body (which is called by prepare())
+    req = requests.Request('POST', 'http://httpbin.org/post', data=data)
+    prepared = req.prepare()
+    
+    # If it was treated as a stream, prepared.body should be the data object itself
+    # (or at least not converted to a string/bytes via urlencode)
+    assert prepared.body == data
+    print("Test passed!")
+
+if __name__ == "__main__":
+    test_proxy_iterable()

Test output

show
........................................................................ [ 21%]
.................................................spytest-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 ===============================
../../../../../../kaggle/tmp/envs/requests/lib/python3.13/site-packages/_pytest/config/__init__.py:1464
  /kaggle/tmp/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/eval8_submission_v1a/requests_7433/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/eval8_submission_v1a/requests_7433/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/eval8_submission_v1a/requests_7433/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/eval8_submission_v1a/requests_7433/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
  /kaggle/tmp/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
337 passed, 1 skipped, 1 xfailed, 13 warnings in 39.04s
[2026-09-26 02:30:19,407] 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 - - [26/Sep/2026 02:30:20] "GET /get?f%C3%B8%C3%B8=f%C3%B8%C3%B8 HTTP/1.1" 200 340
127.0.0.1 - - [26/Sep/2026 02:30:20] "GET /get HTTP/1.1" 200 272
127.0.0.1 - - [26/Sep/2026 02:30:50] "GET /redirect-to?url=http%3A%2F%2F%3A1 HTTP/1.1" 302 0