aio-libs/aiohttp · error · TypeError
Unable to decode.
Error message
Unable to decode.
What it means
BodyPartReaderPayload.decode() unconditionally raises TypeError. This payload type wraps a streaming BodyPartReader and cannot be materialized into a str because it would force buffering arbitrarily large part bodies into memory.
Solutions
- Consume the part via the streaming API: async for chunk in part: ... or await part.write(writer).
- If you truly need a string, read all chunks manually then decode once: data = b''.join([c async for c in part]); data.decode().
- Do not pass BodyPartReaderPayload to APIs that call decode()/as_bytes(); pass a BytesPayload instead.
Example fix
// before
payload = BodyPartReaderPayload(part)
text = payload.decode()
// after
chunks = []
async for chunk in part:
chunks.append(chunk)
text = b''.join(chunks).decode('utf-8') Defensive patterns
Strategy: type-guard
Validate before calling
from aiohttp.multipart import BodyPartReaderPayload, Payload
def materialize_text(payload) -> str:
if isinstance(payload, BodyPartReaderPayload):
raise TypeError('Use write() to stream BodyPartReaderPayload, not decode()')
return payload.decode() Type guard
from aiohttp.multipart import BodyPartReaderPayload
def is_streaming_payload(p: object) -> bool:
return isinstance(p, BodyPartReaderPayload) Try / catch
try:
text = payload.decode()
except TypeError as e:
if 'Unable to decode' in str(e):
# drain the streaming part manually
chunks = []
async for c in payload._value:
chunks.append(c)
text = b''.join(chunks).decode('utf-8')
else:
raise Prevention
- Never call .decode() on a BodyPartReaderPayload; consume it via write() or read_chunk().
- Type-check payloads before passing them to generic serializers that call decode().
- If you need a string from a streaming part, drain chunks yourself and decode once.
When it happens
Trigger: Calling .decode() on a payload obtained from MultipartReader.next() (a BodyPartReader wrapped in BodyPartReaderPayload), e.g. when passing the payload to an API that calls .decode() internally.
Common situations: Using a multipart part payload where a buffered bytes/str payload is expected; piping a BodyPartReaderPayload into helpers that materialize content via decode().
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Unable to read body part as bytes. Use write() to consume.
- Cannot create payload from %r
- Unable to decode - content not cached. Call as_bytes()…
- Can not serialize value type: %r headers: %r value: %r
- Unsupported order
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/9a1938c9700e73c1.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/multipart.py:652
@payload_type(BodyPartReader, order=Order.try_first)
class BodyPartReaderPayload(Payload):
_value: BodyPartReader
# _autoclose = False (inherited) - Streaming reader that may have resources
def __init__(self, value: BodyPartReader, *args: Any, **kwargs: Any) -> None:
super().__init__(value, *args, **kwargs)
params: dict[str, str] = {}
if value.name is not None:
params["name"] = value.name
if value.filename is not None:
params["filename"] = value.filename
if params:
self.set_content_disposition("attachment", True, **params)
def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str:
raise TypeError("Unable to decode.")
async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes:
"""Raises TypeError as body parts should be consumed via write().
This is intentional: BodyPartReader payloads are designed for streaming
large data (potentially gigabytes) and must be consumed only once via
the write() method to avoid memory exhaustion. They cannot be buffered
in memory for reuse.
"""
raise TypeError("Unable to read body part as bytes. Use write() to consume.")
async def write(self, writer: AbstractStreamWriter) -> None:
field = self._value
# Reading the part drains the underlying stream irreversibly, so mark the
# payload consumed up front: even an interrupted write leaves nothing that
# a retry or redirect could replay.
self._consumed = True
while chunk := await field.read_chunk(size=DEFAULT_CHUNK_SIZE):View on GitHub (pinned to d041d4d0fd)