aio-libs/aiohttp · error · ValueError
data cannot be decoded with
Error message
data cannot be decoded with %s encoding
What it means
Raised by BodyPartReader.form() when the collected form-urlencoded bytes cannot be decoded using the resolved charset (explicit encoding argument, the part's Content-Type charset, or UTF-8 default). The original UnicodeDecodeError is converted to a ValueError.
Solutions
- Pass the correct encoding explicitly: await part.form(encoding='latin-1').
- Fix the producer to send a Content-Type charset that matches the bytes, or to send UTF-8.
- If you need raw bytes, use read(decode=False) instead of form() and decode defensively with errors='replace'.
- Validate the declared charset against the bytes (try .decode) before calling form().
Example fix
// before form = await part.form() // after form = await part.form(encoding='latin-1')
Defensive patterns
Strategy: validation
Validate before calling
async def safe_form(part, *, fallback='utf-8'):
raw = await part.read(decode=True)
enc = part.get_charset(default=fallback)
try:
raw.decode(enc)
except UnicodeDecodeError:
enc = fallback
return await part.form(encoding=enc) Type guard
def decodable_with(raw: bytes, encoding: str) -> bool:
try:
raw.decode(encoding)
except UnicodeDecodeError:
return False
return True Try / catch
try:
form = await part.form(encoding='utf-8')
except ValueError as e:
# bytes are not valid in the declared charset
form = await part.form(encoding='latin-1') # fallback Prevention
- Send a Content-Type charset that matches the actual bytes.
- Pass the explicit encoding argument to form() when you know it.
- Read raw bytes with read(decode=False) and decode defensively when charset is untrusted.
When it happens
Trigger: Calling await part.form() on a part whose bytes are not valid in the declared charset; e.g. Latin-1 bytes with a UTF-8 Content-Type, or binary garbage in a text field.
Common situations: Browsers sending form fields in the page's legacy encoding while the part header claims UTF-8; misconfigured server-side charset; binary data posted into a text form control; cross-encoding proxies.
Related errors
- Invalid default charset
- Can not serialize value type: %r headers: %r value: %r
- content_type must be an instance of str. Got
- filename must be an instance of str. Got
- Multipart field missing name.
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/055b980742b114c8.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/multipart.py:517
data = await self.read(decode=True)
if not data:
return None
encoding = encoding or self.get_charset(default="utf-8")
return cast(dict[str, Any], json.loads(data.decode(encoding)))
async def form(self, *, encoding: str | None = None) -> list[tuple[str, str]]:
"""Like read(), but assumes that body parts contain form urlencoded data."""
data = await self.read(decode=True)
if not data:
return []
if encoding is not None:
real_encoding = encoding
else:
real_encoding = self.get_charset(default="utf-8")
try:
decoded_data = data.rstrip().decode(real_encoding)
except UnicodeDecodeError:
raise ValueError("data cannot be decoded with %s encoding" % real_encoding)
return parse_qsl(
decoded_data,
keep_blank_values=True,
encoding=real_encoding,
)
def at_eof(self) -> bool:
"""Returns True if the boundary was reached or False otherwise."""
return self._at_eof
def _apply_content_transfer_decoding(self, data: bytes) -> bytes:
"""Apply Content-Transfer-Encoding decoding if header is present."""
if CONTENT_TRANSFER_ENCODING in self.headers:
return self._decode_content_transfer(data)
return data
def _needs_content_decoding(self) -> bool:View on GitHub (pinned to d041d4d0fd)