psf/requests · error · ValueError

You can only send PreparedRequests.

Error message

You can only send PreparedRequests.

What it means

Raised as a plain `ValueError` from `Session.send()` in `src/requests/sessions.py:768`. `Session.send()` transmits an already-prepared request; it guards against being handed a raw `requests.Request` (which lacks a URL-encoded body, merged headers, and cookies) because the two types are easy to confuse. The public API path is `Session.request()`/`requests.get()` etc., which prepare the request for you.

Source

Thrown at src/requests/sessions.py:768

        return self.request("DELETE", url, **kwargs)

    def send(self, request: PreparedRequest, **kwargs: Any) -> Response:
        """Send a given PreparedRequest.

        :rtype: requests.Response
        """
        # Set defaults that the hooks can utilize to ensure they always have
        # the correct parameters to reproduce the previous request.
        kwargs.setdefault("stream", self.stream)
        kwargs.setdefault("verify", self.verify)
        kwargs.setdefault("cert", self.cert)
        if "proxies" not in kwargs:
            kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)

        # It's possible that users might accidentally send a Request object.
        # Guard against that specific failure case.
        if isinstance(request, Request):
            raise ValueError("You can only send PreparedRequests.")

        assert _is_prepared(request)

        # Set up variables needed for resolve_redirects and dispatching of hooks
        allow_redirects = kwargs.pop("allow_redirects", True)
        stream = kwargs.get("stream")
        hooks = request.hooks

        # Get the appropriate adapter to use
        adapter = self.get_adapter(url=request.url)

        # Start time (approximately) of the request
        start = preferred_clock()

        # Send the request
        r = adapter.send(request, **kwargs)

        # Total elapsed time of the request (approximately)

View on GitHub ↗ (pinned to 414f0513c3)

Solutions

  1. Prepare the request first: `prepped = session.prepare_request(req); resp = session.send(prepped)` — prefer `session.prepare_request` over `req.prepare()` so session cookies/headers merge.
  2. If you don't need the prepared-request pattern, call `session.request('GET', url, ...)` or `requests.get(url)` instead of `send()`.

Example fix

# before
req = requests.Request('GET', url, headers=h)
resp = session.send(req)
# after
req = requests.Request('GET', url, headers=h)
prepped = session.prepare_request(req)
resp = session.send(prepped)
Defensive patterns

Strategy: type-guard

Validate before calling

from requests import Request, PreparedRequest
req = Request('GET', url).prepare()  # prepare() before session.send()
assert isinstance(req, PreparedRequest)

Type guard

from requests import PreparedRequest
def is_prepared(req):
    return isinstance(req, PreparedRequest)

Try / catch

try:
    resp = session.send(req)
except ValueError as e:
    if 'PreparedRequests' in str(e):
        resp = session.send(session.prepare_request(req))
    else:
        raise

Prevention

When it happens

Trigger: Calling `session.send(req)` where `req = requests.Request('GET', url)` was never passed through `session.prepare_request(req)` or `req.prepare()`. Common in code using the prepared-request pattern for header inspection, custom auth signing, or test frameworks that build Request objects manually.

Common situations: Following the prepared-request docs but skipping the `prepare_request()` step; refactors that changed a variable from the prepared to the raw request; middleware/instrumentation that intercepts and re-sends requests; mocking layers that hand back a Request instead of a PreparedRequest.

Related errors


AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31). Data as JSON: /data/errors/f86ed6905d7e9161.json. Report an issue: GitHub ↗.