aio-libs/aiohttp · error · TypeError

content_type must be an instance of str. Got

Error message

content_type must be an instance of str. Got: %s

What it means

FormData.add_field raises TypeError when content_type is not None and not a str instance. The content_type becomes the Content-Type header value for the multipart part, so it must be a plain string such as 'image/png'. This is a type validation guard.

Solutions

  1. Convert the content type to str: form.add_field('file', data, content_type=str(ct))
  2. If using an Enum, ensure it has a string value and pass .value
  3. Hard-code the content type string if it is a constant

Example fix

# before
from enum import Enum
class CT(Enum):
    PNG = 'image/png'
form.add_field('img', data, content_type=CT.PNG)

# after
form.add_field('img', data, content_type=CT.PNG.value)
Defensive patterns

Strategy: type-guard

Validate before calling

def coerce_content_type(ct):
    if ct is None:
        return None
    if isinstance(ct, bytes):
        return ct.decode('utf-8')
    if not isinstance(ct, str):
        return str(ct)
    return ct

form.add_field('file', data, content_type=coerce_content_type(ct))

Type guard

def is_valid_content_type(ct) -> bool:
    return ct is None or isinstance(ct, str)

Prevention

When it happens

Trigger: Passing a non-str object as content_type, such as an enum member, bytes, or a custom class with a __str__ method. Common when content types come from a configuration object or enum without conversion.

Common situations: Using an Enum for content types and passing the member directly; passing bytes from a header parsing result; accidentally passing a tuple of (type, subtype).

Related errors


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

Appendix: source

Thrown at aiohttp/formdata.py:74

    ) -> None:
        if isinstance(value, (io.IOBase, bytes, bytearray, memoryview)):
            self._is_multipart = True

        _safe_header(name)
        type_options: MultiDict[str] = MultiDict({"name": name})
        if filename is not None and not isinstance(filename, str):
            raise TypeError("filename must be an instance of str. Got: %s" % filename)
        if filename is None and isinstance(value, io.IOBase):
            filename = guess_filename(value, name)
        if filename is not None:
            _safe_header(filename)
            type_options["filename"] = filename
            self._is_multipart = True

        headers = {}
        if content_type is not None:
            if not isinstance(content_type, str):
                raise TypeError(
                    "content_type must be an instance of str. Got: %s" % content_type
                )
            _safe_header(content_type)
            headers[hdrs.CONTENT_TYPE] = content_type
            self._is_multipart = True

        self._fields.append((type_options, headers, value))

    def add_fields(self, *fields: Any) -> None:
        to_add: deque[Any] = deque(fields)

        while to_add:
            rec = to_add.popleft()

            if isinstance(rec, io.IOBase):
                k = guess_filename(rec, "unknown")
                self.add_field(k, rec)  # type: ignore[arg-type]

View on GitHub (pinned to d041d4d0fd)