psf/requests · error · UnrewindableBodyError

Unable to rewind request body for redirect.

Error message

Unable to rewind request body for redirect.

What it means

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.

Solutions

  1. Materialize the body into bytes before the request: data=b''.join(generator) or data=list(generator).
  2. Use a seekable BytesIO wrapper: from io import BytesIO; data=BytesIO(original_bytes).
  3. Set allow_redirects=False for non-rewindable bodies and handle redirects manually by re-creating the body each time.

Example fix

# before
def gen():
    yield b'chunk1'
    yield b'chunk2'
resp = session.post(url, data=gen())  # UnrewindableBodyError on redirect

# after — materialize
resp = session.post(url, data=b''.join(gen()))

# or disable redirects
resp = session.post(url, data=gen(), allow_redirects=False)
Defensive patterns

Strategy: fallback

Validate before calling

# Materialize non-seekable bodies before redirecting requests
body_bytes = b''.join(generator) if hasattr(generator, '__next__') else generator
resp = session.post(url, data=body_bytes)

Type guard

def is_seekable(body) -> bool:
    return hasattr(body, 'seek') and hasattr(body, 'read')

Try / catch

try:
    resp = session.post(url, data=body)
except requests.exceptions.UnrewindableBodyError:
    # Fallback: disable redirects and handle manually
    resp = session.post(url, data=body, allow_redirects=False)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of psf/requests@8068356288 (2026-08-11). Data as JSON: /api/errors/e35d8ce52f5ac8c3. Report an issue: GitHub.

Appendix: source

Thrown at src/requests/utils.py:1155


def rewind_body(prepared_request: PreparedRequest) -> None:
    """Move file pointer back to its recorded starting position
    so it can be read again on redirect.
    """
    body_seek = getattr(prepared_request.body, "seek", None)
    if body_seek is not None and isinstance(
        prepared_request._body_position,
        integer_types,
    ):
        try:
            body_seek(prepared_request._body_position)
        except OSError:
            raise UnrewindableBodyError(
                "An error occurred when rewinding request body for redirect."
            )
    else:
        raise UnrewindableBodyError("Unable to rewind request body for redirect.")

View on GitHub (pinned to 8068356288)