httpx streaming response: stream(), iter_bytes, ResponseNotRead (tested)
Use with client.stream("GET", url) as r: and iterate r.iter_bytes() / r.iter_lines() inside the block (async: aiter_bytes()). Accessing r.text there raises httpx.ResponseNotRead unless you call r.read() first.
Streaming in httpx means not loading the whole body into memory: httpx.stream() / client.stream() return the response as soon as the headers arrive, and you iterate over the body. Measured with httpx 0.28.1 on Python 3.13.5 against a local http.server that returns three lines (21 bytes).
1. ResponseNotRead: .text/.json() inside a stream
import httpx
from srv import start
base = start() # returns "line 1\nline 2\nline 3\n"
with httpx.stream("GET", base) as r:
try:
print(r.text)
except httpx.ResponseNotRead as e:
print(type(e).__name__ + ":", e)
Output:
ResponseNotRead: Attempted to access streaming response content, without having called `read()`.
Inside a stream() block the body has not been read, so .text, .content and .json() raise httpx.ResponseNotRead. Either iterate (below) or call r.read() first; if you always need the whole body, use a normal client.get() instead.
2. iter_lines, iter_bytes(chunk_size) and read()
import httpx
from srv import start
base = start()
with httpx.Client() as client:
with client.stream("GET", base) as r:
print(r.status_code, r.headers["content-length"])
for line in r.iter_lines():
print(repr(line))
with client.stream("GET", base) as r:
print([len(c) for c in r.iter_bytes(chunk_size=8)])
with client.stream("GET", base) as r:
r.read() # load the body, then .text works
print(repr(r.text[:6]))
Output:
200 21
'line 1'
'line 2'
'line 3'
[8, 8, 5]
'line 1'
Status and headers are available before the body. iter_lines() yields decoded lines without the newline; iter_bytes(chunk_size=8) yields chunks of that size (the last one shorter); iter_text() and iter_raw() (no gzip/deflate decoding) also exist. To download a file, write each iter_bytes() chunk to an open file.
3. StreamConsumed and StreamClosed
import httpx
from srv import start
base = start()
with httpx.stream("GET", base) as r:
list(r.iter_bytes())
try:
list(r.iter_bytes())
except httpx.StreamConsumed as e:
print(type(e).__name__ + ":", str(e)[:80])
with httpx.stream("GET", base) as r:
pass
try:
r.read()
except httpx.StreamClosed as e:
print(type(e).__name__ + ":", str(e)[:80])
Output:
StreamConsumed: Attempted to read or stream some content, but the content has already been strea
StreamClosed: Attempted to read or stream content, but the stream has been closed.
A stream can be iterated once: a second pass raises StreamConsumed. After the with block exits, the connection is released and reading raises StreamClosed, so do all reading inside the block.
4. Async: client.stream with aiter_lines / aiter_bytes
import asyncio, httpx
from srv import start
base = start()
async def main():
async with httpx.AsyncClient() as client:
async with client.stream("GET", base) as r:
async for line in r.aiter_lines():
print(line)
asyncio.run(main())
Output:
line 1
line 2
line 3
With AsyncClient, use async with client.stream(...) and the aiter_* methods (aiter_bytes, aiter_text, aiter_lines, aread()).
Related: httpx timeout, httpx RemoteProtocolError, httpx vs requests.
Frequently asked questions
Why does httpx raise ResponseNotRead?
You accessed .text, .content or .json() on a streaming response without calling read() first.
How do I stream a download with httpx?
Open client.stream('GET', url) in a with block and write each chunk from r.iter_bytes() to a file.
Can I iterate an httpx stream twice?
No. A second iteration raised StreamConsumed; reading after the with block raised StreamClosed.
Written with AI assistance. Measured on 2026-10-23 with httpx 0.28.1 on Python 3.13.5 against a local http.server. Each block was run verbatim and the output pasted unedited. PlainHeaders is our extension; httpx is free.