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

  1. Pass the correct encoding explicitly: await part.form(encoding='latin-1').
  2. Fix the producer to send a Content-Type charset that matches the bytes, or to send UTF-8.
  3. If you need raw bytes, use read(decode=False) instead of form() and decode defensively with errors='replace'.
  4. 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

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


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)