aio-libs/aiohttp · error · TypeError
Unable to decode - content not cached. Call as_bytes() first
Error message
Unable to decode - content not cached. Call as_bytes() first.
What it means
Raised by AsyncIterablePayload.decode() when the payload has not yet been materialized into a cache. AsyncIterablePayload streams lazily and only populates _cached_chunks after as_bytes() runs; decode() is a synchronous helper that needs that cache to exist. Calling decode() on a fresh or write-only payload therefore fails. The fix is to call the async as_bytes() first, which populates the cache and makes decode() reusable.
Source
Thrown at aiohttp/payload.py:1075
await writer.write(chunk)
# If we have a content length limit
elif remaining_bytes > 0:
await writer.write(chunk[:remaining_bytes])
remaining_bytes -= len(chunk)
# We still want to exhaust the iterator even
# if we have reached the content length limit
# since the file handle may not get closed by
# the iterator if we don't do this
except StopAsyncIteration:
# Iterator is exhausted
self._iter = None
self._consumed = True # Mark as consumed when streamed without caching
def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
"""Decode the payload content as a string if cached chunks are available."""
if self._cached_chunks is not None:
return b"".join(self._cached_chunks).decode(encoding, errors)
raise TypeError("Unable to decode - content not cached. Call as_bytes() first.")
async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
"""
Return bytes representation of the value.
This method reads the entire async iterable content and returns it as bytes.
It generates and caches the chunks for future reuse.
"""
# If we have cached chunks, return them joined
if self._cached_chunks is not None:
return b"".join(self._cached_chunks)
# If iterator is exhausted and no cache, return empty
if self._iter is None:
return b""
# Read all chunks and cache them
chunks: list[bytes] = []View on GitHub (pinned to c0ef574e29)
Solutions
- Call `await payload.as_bytes()` first to populate the cache, then payload.decode() works.
- If you only need the string, use `data = await payload.as_bytes(); text = data.decode('utf-8')` directly.
- Avoid decode() for streaming payloads you intend to also send — materializing consumes the iterator.
- Use BytesPayload instead if you need synchronous decode() support from the start.
Example fix
// before
text = payload.decode('utf-8') # raises TypeError
// after
data = await payload.as_bytes()
text = data.decode('utf-8') Defensive patterns
Strategy: validation
Validate before calling
# Always materialize before sync decode:
if payload._cached_chunks is None:
data = await payload.as_bytes() # populates cache
text = payload.decode() if payload._cached_chunks is not None else data.decode() Try / catch
try:
text = payload.decode()
except TypeError:
data = await payload.as_bytes()
text = data.decode('utf-8') Prevention
- Treat decode() as available only after as_bytes() for streaming payloads.
- Prefer BytesPayload when you need synchronous decode().
- Wrap decode() in a helper that falls back to as_bytes().
When it happens
Trigger: Calling payload.decode() directly on an AsyncIterablePayload (or StreamReaderPayload) that was streamed via write() without ever calling as_bytes(). Reproducible when a test or middleware tries to log the body as text after the payload was consumed by the writer.
Common situations: Debug logging that wants to peek at the body; tests that assert on payload content; middleware that reads body for inspection; using decode() (sync) instead of as_bytes() (async) on a streaming payload.
Related errors
- Unable to decode.
- Unable to read body part as bytes. Use write() to consume.
- value argument must support collections.abc.AsyncIterable in
- Cannot follow redirect with a consumed request body. Use byt
- Can not serialize value type: %r headers: %r value: %r
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/8c9d8667e471d70c.json.
Report an issue: GitHub.