aio-libs/aiohttp · error · ValueError

boundary should contain ASCII only chars

Error message

boundary should contain ASCII only chars

What it means

MultipartWriter.__init__ requires the boundary string to be ASCII-encodable, because the underlying Payload API serializes the boundary as a str and writes it into the Content-Type header. If boundary.encode('ascii') raises UnicodeEncodeError, ValueError is raised.

Solutions

  1. Pass an ASCII-only boundary (RFC 2046 bchars): digits, letters, and '()+,-./_:;=?\''.
  2. Omit the boundary argument and let MultipartWriter generate one via uuid.uuid4().hex.
  3. Sanitize user input before using it as a boundary: re.sub(r'[^A-Za-z0-9\'()+,-./_:;=?]', '', value).
  4. Validate with value.isascii() before constructing the writer.

Example fix

// before
mw = MultipartWriter(boundary='CAFE-菜单')

// after
mw = MultipartWriter()  # auto-generated ASCII boundary
Defensive patterns

Strategy: validation

Validate before calling

import re
_BCHARS = re.compile(r"[A-Za-z0-9'()+,./_:;=?-]+\Z")

def sanitize_boundary(value: str) -> str:
    cleaned = re.sub(r"[^A-Za-z0-9'()+,./_:;=?-]", '', value)
    return cleaned or uuid.uuid4().hex

Type guard

def is_ascii_boundary(value: object) -> bool:
    return isinstance(value, str) and value.isascii()

Try / catch

try:
    mw = MultipartWriter(boundary=candidate)
except ValueError as e:
    if 'ASCII' in str(e):
        mw = MultipartWriter()  # fall back to autogenerated ASCII boundary
    else:
        raise

Prevention

When it happens

Trigger: Passing a boundary containing non-ASCII characters (e.g. emoji, accented letters, CJK) to MultipartWriter(subtype=..., boundary=...).

Common situations: Deriving the boundary from user-supplied data, file names, or locale-specific strings; copy-pasting a boundary from a non-ASCII source.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/74e55085e55a6e54. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/multipart.py:928

class MultipartWriter(Payload):
    """Multipart body writer."""

    _value: None
    # _consumed = False (inherited) - Can be encoded multiple times
    _autoclose = True  # No file handles, just collects parts in memory

    def __init__(self, subtype: str = "mixed", boundary: str | None = None) -> None:
        boundary = boundary if boundary is not None else uuid.uuid4().hex
        # The underlying Payload API demands a str (utf-8), not bytes,
        # so we need to ensure we don't lose anything during conversion.
        # As a result, require the boundary to be ASCII only.
        # In both situations.

        try:
            self._boundary = boundary.encode("ascii")
        except UnicodeEncodeError:
            raise ValueError("boundary should contain ASCII only chars") from None

        if len(boundary) > 70:
            raise ValueError("boundary %r is too long (70 chars max)" % boundary)

        ctype = f"multipart/{subtype}; boundary={self._boundary_value}"

        super().__init__(None, content_type=ctype)

        self._parts: list[_Part] = []
        self._is_form_data = subtype == "form-data"

    def __enter__(self) -> "MultipartWriter":
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,

View on GitHub (pinned to d041d4d0fd)