encode/httpx · error · StreamConsumed
Attempted to read or stream some content, but the content ha
Error message
Attempted to read or stream some content, but the content has already been streamed. For requests, this could be due to passing a generator as request content, and then receiving a redirect response or a secondary request as part of an authentication flow.For responses, this could be due to attempting to stream the response content more than once.
What it means
Raised as httpx.StreamConsumed by IteratorByteStream.__iter__ (and the async variant) when a generator-backed body stream is iterated a second time. Generators cannot be replayed, so once consumed the stream is exhausted; httpx guards re-iteration for generator sources (file-like sources are exempt because they expose .read).
Source
Thrown at httpx/_content.py:52
def __iter__(self) -> Iterator[bytes]:
yield self._stream
async def __aiter__(self) -> AsyncIterator[bytes]:
yield self._stream
class IteratorByteStream(SyncByteStream):
CHUNK_SIZE = 65_536
def __init__(self, stream: Iterable[bytes]) -> None:
self._stream = stream
self._is_stream_consumed = False
self._is_generator = inspect.isgenerator(stream)
def __iter__(self) -> Iterator[bytes]:
if self._is_stream_consumed and self._is_generator:
raise StreamConsumed()
self._is_stream_consumed = True
if hasattr(self._stream, "read"):
# File-like interfaces should use 'read' directly.
chunk = self._stream.read(self.CHUNK_SIZE)
while chunk:
yield chunk
chunk = self._stream.read(self.CHUNK_SIZE)
else:
# Otherwise iterate.
for part in self._stream:
yield part
class AsyncIteratorByteStream(AsyncByteStream):
CHUNK_SIZE = 65_536
def __init__(self, stream: AsyncIterable[bytes]) -> None:View on GitHub (pinned to b5addb64f0)
Solutions
- Buffer the body as bytes (content=b"...") or a seekable file object instead of a raw generator.
- Wrap the generator so the body can be replayed, or read it fully before sending if redirects are expected.
- For responses, call response.read()/aread() once and reuse response.content.
Example fix
// before def gen(): yield b"chunk" client.post(url, content=gen(), follow_redirects=True) # StreamConsumed on redirect // after client.post(url, content=b"chunk", follow_redirects=True)
Defensive patterns
Strategy: validation
Validate before calling
import inspect
def is_replayable(content) -> bool:
# generators/async generators cannot be replayed once consumed
if inspect.isgenerator(content) or inspect.isasyncgen(content):
return False
return True
# before sending with redirects or auth that may resend the body
assert is_replayable(content) or not follow_redirects, \
"generator content cannot survive a redirect/auth resend; pass bytes or a file" Type guard
import inspect
def is_replayable_content(content) -> bool:
if inspect.isgenerator(content) or inspect.isasyncgen(content):
return False
return True Try / catch
try:
resp = client.post(url, content=body, follow_redirects=True)
except httpx.StreamConsumed:
# body was a one-shot generator; retry with buffered bytes
resp = client.post(url, content=buffered_bytes, follow_redirects=True) Prevention
- Avoid raw generators for request bodies when redirects or auth resends are possible.
- Buffer upload bodies to bytes, or use a seekable file object.
- Read streamed responses once and reuse .content instead of re-iterating.
When it happens
Trigger: Passing a generator as request content= and then hitting a redirect or an auth retry that must resend the body (the generator is already exhausted); or iterating response content twice (calling response.read()/aiter() again on a generator-backed stream).
Common situations: Streaming uploads that get redirected; DigestAuth challenges requiring a body resend; calling .iter_bytes() / .read() on the same streamed response more than once.
Related errors
- Attempted to send an async request with a sync Client instan
- Attempted to send a sync request with an AsyncClient instanc
- Attempted to read or stream content, but the stream has been
- Unexpected type for 'content', {type(content)!r}
- Attempted to access streaming request content, without havin
AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04).
Data as JSON: /data/errors/e2e7423f1a798fb9.json.
Report an issue: GitHub.