encode/httpx · error · ResponseNotRead

Attempted to access streaming response content, without havi

Error message

Attempted to access streaming response content, without having called `read()`.

What it means

Raised as `ResponseNotRead` (a `StreamError` subclass) by the `Response.content` property when no `_content` attribute exists yet. For a streaming response (one whose body has not been buffered), httpx will not implicitly read the network; you must call `.read()` (sync) or `.aread()` (async) first. Accessing `.content`, `.text`, or `.json()` before that triggers this error.

Source

Thrown at httpx/_models.py:638

    def reason_phrase(self) -> str:
        try:
            reason_phrase: bytes = self.extensions["reason_phrase"]
        except KeyError:
            return codes.get_reason_phrase(self.status_code)
        else:
            return reason_phrase.decode("ascii", errors="ignore")

    @property
    def url(self) -> URL:
        """
        Returns the URL for which the request was made.
        """
        return self.request.url

    @property
    def content(self) -> bytes:
        if not hasattr(self, "_content"):
            raise ResponseNotRead()
        return self._content

    @property
    def text(self) -> str:
        if not hasattr(self, "_text"):
            content = self.content
            if not content:
                self._text = ""
            else:
                decoder = TextDecoder(encoding=self.encoding or "utf-8")
                self._text = "".join([decoder.decode(self.content), decoder.flush()])
        return self._text

    @property
    def encoding(self) -> str | None:
        """
        Return an encoding to use for decoding the byte content into text.
        The priority for determining this is given by...

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Inside a streaming context, call `response.read()` (sync) or `await response.aread()` (async) before accessing `.content`/`.text`/`.json()`.
  2. If you always want the full body, drop `stream=True` / `client.stream(...)` and use plain `client.get(...)` which reads automatically.
  3. For an already-closed stream, re-issue the request rather than retrying `.read()`.
  4. Use `iter_raw`/`iter_bytes`/`aiter_bytes` if you want to consume the body incrementally instead of buffering.

Example fix

// before
with client.stream('GET', url) as r:
    pass
print(r.text)  # ResponseNotRead

// after
with client.stream('GET', url) as r:
    r.read()
print(r.text)
Defensive patterns

Strategy: validation

Validate before calling

def is_read(resp: httpx.Response) -> bool:
    return hasattr(resp, '_content')

Type guard

import httpx

def response_is_buffered(resp: httpx.Response) -> bool:
    return hasattr(resp, '_content')

Try / catch

try:
    body = response.content
except httpx.ResponseNotRead:
    await response.aread()  # or response.read() for sync
    body = response.content

Prevention

When it happens

Trigger: Calling `client.stream('GET', url)` (or `client.send(req, stream=True)`), then inside or after the context reading `response.text`/`response.content`/`response.json()` without first awaiting/calling `response.aread()`/`response.read()`. Also reproducible by manually building `httpx.Response(200, stream=...)` and touching `.content`.

Common situations: Migrating from `requests` where `r.text` lazily read the body; forgetting that `with client.stream(...)` does NOT auto-read the body; mixing sync `.read()` with an async client.

Related errors


AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04). Data as JSON: /data/errors/18ce8ab2b718d4b1.json. Report an issue: GitHub.