{"record":{"id":"e35d8ce52f5ac8c3","repo":"psf/requests","slug":"unable-to-rewind-request-body-for-redirect","errorCode":null,"errorMessage":"Unable to rewind request body for redirect.","messagePattern":"Unable to rewind request body for redirect\\.","errorType":"exception","errorClass":"UnrewindableBodyError","httpStatus":null,"severity":"error","filePath":"src/requests/utils.py","lineNumber":1155,"sourceCode":"\n\ndef rewind_body(prepared_request: PreparedRequest) -> None:\n    \"\"\"Move file pointer back to its recorded starting position\n    so it can be read again on redirect.\n    \"\"\"\n    body_seek = getattr(prepared_request.body, \"seek\", None)\n    if body_seek is not None and isinstance(\n        prepared_request._body_position,\n        integer_types,\n    ):\n        try:\n            body_seek(prepared_request._body_position)\n        except OSError:\n            raise UnrewindableBodyError(\n                \"An error occurred when rewinding request body for redirect.\"\n            )\n    else:\n        raise UnrewindableBodyError(\"Unable to rewind request body for redirect.\")\n","sourceCodeStart":1137,"sourceCodeEnd":1156,"githubUrl":"https://github.com/psf/requests/blob/8068356288978c4f54661ae6f95afe0e0831885e/src/requests/utils.py#L1137-L1156","documentation":"Raised by rewind_body() when either the request body has no seek() method (non-seekable) or _body_position was not recorded (is None or not an integer). Without both conditions, the body cannot be rewound for a redirect. This is distinct from error 53: here the capability is structurally absent, not broken at runtime.","triggerScenarios":"Redirect after a POST/PUT whose body is a generator, iterator, or other non-seekable object; a body from a pipe (subprocess stdout); a streaming source that was consumed and cannot be replayed; _body_position is None because the body was set after prepare.","commonSituations":"Posting a generator or iterator as the request body with redirects enabled; streaming data from a real-time source that can't be replayed; using data=iter([...]) instead of a seekable file or bytes; uploading from stdin or a socket.","solutions":["Materialize the body into bytes before the request: data=b''.join(generator) or data=list(generator).","Use a seekable BytesIO wrapper: from io import BytesIO; data=BytesIO(original_bytes).","Set allow_redirects=False for non-rewindable bodies and handle redirects manually by re-creating the body each time."],"exampleFix":"# before\ndef gen():\n    yield b'chunk1'\n    yield b'chunk2'\nresp = session.post(url, data=gen())  # UnrewindableBodyError on redirect\n\n# after — materialize\nresp = session.post(url, data=b''.join(gen()))\n\n# or disable redirects\nresp = session.post(url, data=gen(), allow_redirects=False)","handlingStrategy":"fallback","validationCode":"# Materialize non-seekable bodies before redirecting requests\nbody_bytes = b''.join(generator) if hasattr(generator, '__next__') else generator\nresp = session.post(url, data=body_bytes)","typeGuard":"def is_seekable(body) -> bool:\n    return hasattr(body, 'seek') and hasattr(body, 'read')","tryCatchPattern":"try:\n    resp = session.post(url, data=body)\nexcept requests.exceptions.UnrewindableBodyError:\n    # Fallback: disable redirects and handle manually\n    resp = session.post(url, data=body, allow_redirects=False)","preventionTips":["Materialize generators/iterators into bytes before posting if redirects are possible.","Wrap seekable data in io.BytesIO for rewinding capability.","Set allow_redirects=False when body cannot be replayed and handle redirects manually.","Avoid piping/streaming sources as request bodies when redirects are enabled."],"tags":["unrewindable-body","redirect","non-seekable","body-stream","generator"],"backgroundTag":null,"analyzedSha":"8068356288978c4f54661ae6f95afe0e0831885e","analyzedAt":"2026-08-11T20:11:09.238Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}