{"id":"e2e7423f1a798fb9","repo":"encode/httpx","slug":"attempted-to-read-or-stream-some-content-but-the","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"exception","errorClass":"StreamConsumed","httpStatus":null,"severity":"error","filePath":"httpx/_content.py","lineNumber":52,"sourceCode":"\n    def __iter__(self) -> Iterator[bytes]:\n        yield self._stream\n\n    async def __aiter__(self) -> AsyncIterator[bytes]:\n        yield self._stream\n\n\nclass IteratorByteStream(SyncByteStream):\n    CHUNK_SIZE = 65_536\n\n    def __init__(self, stream: Iterable[bytes]) -> None:\n        self._stream = stream\n        self._is_stream_consumed = False\n        self._is_generator = inspect.isgenerator(stream)\n\n    def __iter__(self) -> Iterator[bytes]:\n        if self._is_stream_consumed and self._is_generator:\n            raise StreamConsumed()\n\n        self._is_stream_consumed = True\n        if hasattr(self._stream, \"read\"):\n            # File-like interfaces should use 'read' directly.\n            chunk = self._stream.read(self.CHUNK_SIZE)\n            while chunk:\n                yield chunk\n                chunk = self._stream.read(self.CHUNK_SIZE)\n        else:\n            # Otherwise iterate.\n            for part in self._stream:\n                yield part\n\n\nclass AsyncIteratorByteStream(AsyncByteStream):\n    CHUNK_SIZE = 65_536\n\n    def __init__(self, stream: AsyncIterable[bytes]) -> None:","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/encode/httpx/blob/b5addb64f0161ff6bfe94c124ef76f6a1fba5254/httpx/_content.py#L34-L70","documentation":"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).","triggerScenarios":"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).","commonSituations":"Streaming uploads that get redirected; DigestAuth challenges requiring a body resend; calling .iter_bytes() / .read() on the same streamed response more than once.","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."],"exampleFix":"// before\ndef gen(): yield b\"chunk\"\nclient.post(url, content=gen(), follow_redirects=True)  # StreamConsumed on redirect\n// after\nclient.post(url, content=b\"chunk\", follow_redirects=True)","handlingStrategy":"validation","validationCode":"import inspect\n\ndef is_replayable(content) -> bool:\n    # generators/async generators cannot be replayed once consumed\n    if inspect.isgenerator(content) or inspect.isasyncgen(content):\n        return False\n    return True\n\n# before sending with redirects or auth that may resend the body\nassert is_replayable(content) or not follow_redirects, \\\n    \"generator content cannot survive a redirect/auth resend; pass bytes or a file\"","typeGuard":"import inspect\n\ndef is_replayable_content(content) -> bool:\n    if inspect.isgenerator(content) or inspect.isasyncgen(content):\n        return False\n    return True","tryCatchPattern":"try:\n    resp = client.post(url, content=body, follow_redirects=True)\nexcept httpx.StreamConsumed:\n    # body was a one-shot generator; retry with buffered bytes\n    resp = client.post(url, content=buffered_bytes, follow_redirects=True)","preventionTips":["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."],"tags":["streaming","content","redirect"],"analyzedSha":"b5addb64f0161ff6bfe94c124ef76f6a1fba5254","analyzedAt":"2026-08-04T19:32:56.768Z","schemaVersion":2}