resolved RESOLVED PASS · 5 tool calls · 28 s · psf/requests
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 | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"def prepare_body\" ."
} | {"status": "ok", "stdout": "./src/requests/models.py:574: def prepare_body(\n", "exit_code": 0}full{"status": "ok", "stdout": "./src/requests/models.py:574: def prepare_body(\n", "exit_code": 0} |
| 2 | read_file | {
"end_line": 620,
"filepath": "src/requests/models.py",
"start_line": 574
} | {"status": "ok", "filepath": "src/requests/models.py", "content": " def prepare_body(\n self, data: _t.DataType, files: _t.FilesType, json: _t.JsonType = None\nfull{"status": "ok", "filepath": "src/requests/models.py", "content": " 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", "start_line": 574, "end_line": 620, "total_lines": 1180, "is_truncated": false} |
| 3 | read_file | {
"end_line": 650,
"filepath": "src/requests/models.py",
"start_line": 621
} | {"status": "ok", "filepath": "src/requests/models.py", "content": " raise NotImplementedError(\n \"Streamed bodies and files are mutually exclusive.\"\n )\n\n ifull{"status": "ok", "filepath": "src/requests/models.py", "content": " raise NotImplementedError(\n \"Streamed bodies and files are mutually exclusive.\"\n )\n\n if length:\n self.headers[\"Content-Length\"] = builtin_str(length)\n else:\n self.headers[\"Transfer-Encoding\"] = \"chunked\"\n else:\n # After is_stream filtering, remaining data is raw (not streamed)\n raw_data = cast(\"_t.RawDataType | None\", data)\n\n # Multi-part file uploads.\n if files:\n (body, content_type) = self._encode_files(files, raw_data)\n else:\n if raw_data:\n body = self._encode_params(raw_data)\n if isinstance(data, basestring) or isinstance(data, _SupportsRead):\n content_type = None\n else:\n content_type = \"application/x-www-form-urlencoded\"\n\n self.prepare_content_length(body)\n\n # Add content-type if it wasn't explicitly provided.\n if content_type and (\"content-type\" not in self.headers):\n self.headers[\"Content-Type\"] = content_type\n\n self.body = body # type: ignore[assignment] # body transforms from DataType to BodyType\n", "start_line": 621, "end_line": 650, "total_lines": 1180, "is_truncated": false} |
| 4 | read_file | {
"filepath": "src/requests/models.py",
"start_line": 574,
"end_line": 650
} | {"status": "ok", "filepath": "src/requests/models.py", "content": " def prepare_body(\n self, data: _t.DataType, files: _t.FilesType, json: _t.JsonType = None\nfull{"status": "ok", "filepath": "src/requests/models.py", "content": " 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 raise NotImplementedError(\n \"Streamed bodies and files are mutually exclusive.\"\ |
| 5 | edit_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.pfull{"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} |
| 6 | submit_patch | [] | {"status": "ok", "patch_size": 560, "files_changed": 1}full{"status": "ok", "patch_size": 560, "files_changed": 1} |
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:
........................................................................ [ 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/eval_submission_analyzer/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/eval_submission_analyzer/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/eval_submission_analyzer/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/eval_submission_analyzer/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-24 06:50:17,979] 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 - - [24/Sep/2026 06:50:18] "GET /get HTTP/1.1" 200 272
127.0.0.1 - - [24/Sep/2026 06:50:18] "POST /post HTTP/1.1" 200 363
127.0.0.1 - - [24/Sep/2026 06:50:19] "GET /status/204 HTTP/1.1" 204 0